從JDK1.5開始,增加了一個執行並行任務的框架——Executor框架。框架在java.util.concurrent包中。Executor是框架中的一個接口,使用Executor可以同步或異步地執行任務。
異步任務可以放在多線程中處理,但使用Executor比直接創建線程處理任務有很多好處,比如設置任務開始時間,取消任務隊列,控制任務隊列執行策略等,而且使用Executor可以很容易地創建線程池。
線程池
Executors是一個工廠類,可以方便的創建各種類型Executor或線程池:
- newSingleThreadExecutor():創建一個Executor,順序執行一個任務隊列,每次只能執行一個任務。
- newFixedThreadPool(nThreads):創建一個有固定數目線程的線程池,每次最多執行nThreads個任務,如果任務多於nThreads,多於的線程置於等待隊列中
- newCachedThreadPool():創建一個線程池,線程數目會隨任務數目增加而增加,同時也會回收已經空閒的線程。
- newScheduledThreadPool(corePoolSize):創建一個線程池,可以讓任務延遲或週期性執行。
執行單個任務
Executor executor = Executors.newSingleThreadExecutor();
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Executing task...");
}
};
executor.execute(task);
創建一個線程池
final int pool_size = 5;
Executor executor = Executors.newFixedThreadPool(pool_size);
//or
//Executor executor = Executors.newCachedThreadPool();
int nTasks = 10;
for (int i = 0; i < nTasks; ++i){
Runnable task = createTask();
executor.execute(task);
}
生命週期
Executor有個缺點,一旦任務提交,就無法獲得任務的執行情況,也無法停止,除非等待所有任務執行完畢或強制關閉JVM。
ExecutorService擴展了Executor接口,加入了生命週期管理。ExecutorService可以在運行時關閉,關閉後ExecutorService就停止接收新的任務。對已經接收到的任務有兩種處理方法:如果調用shutdown()方法,ExecutorService會等待所有接收到的任務執行完畢;如果調用shutdownNow()方法,ExecutorService會取消所有等待的任務並嘗試停止正在執行的任務。
ExecutorService.java中的例子,關閉線程池:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
延遲、週期性執行任務
ScheduledExecutorService擴展了ExecutorService接口,可以延遲或間期執行任務。
- schedule(command, delay, TimeUnit): 任務延遲delay時間後執行
- scheduleAtFixedRate(command, initDelay, period, TimeUnit.SECONDS): 先延遲initDelay時間,然後每period時間執行一次任務
- scheduleWithFixedDelay(command, initDelay, delay, TimeUnit.SECONDS): 先延遲initDelay時間,然後執行任務,每次執行完畢後延遲delay時間再執行下次任務。
注意:scheduleAtFixedRate和scheduleWithFixedDelay的區別。scheduleAtFixedRate執行任務的週期與任務消耗的時間沒有關係,如果任務消耗10秒,但設置的週期是1秒,那1秒後下次任務就會開始。而scheduleWithFixedDelay不同,一定要等本次任務完成後,間隔delay秒才執行下一個任務。
延遲、週期性執行任務:
final int pool_size = 1;
ScheduledExecutorService scheduleService = Executors.newScheduledThreadPool(pool_size);
Runnable command = new Runnable() {
@Override
public void run() {
System.out.println("Executing task...");
}
};
scheduleService.schedule(command, 10, TimeUnit.SECONDS);
//scheduleService.scheduleAtFixedRate(command, 10, 1, TimeUnit.SECONDS);
//scheduleService.scheduleWithFixedDelay(command, 5, 1, TimeUnit.SECONDS);
參考"Java concurrency in practice" 6.2