1. 程式人生 > >執行緒執行者(二)建立一個執行緒執行者

執行緒執行者(二)建立一個執行緒執行者

宣告:本文是《 Java 7 Concurrency Cookbook 》的第四章,作者: Javier Fernández González     譯者:許巧輝     校對:方騰飛

建立一個執行緒執行者

使用Executor framework的第一步就是建立一個ThreadPoolExecutor類的物件。你可以使用這個類提供的4個構造器或Executors工廠類來 建立ThreadPoolExecutor。一旦有執行者,你就可以提交Runnable或Callable物件給執行者來執行。

在這個指南中,你將會學習如何使用這兩種操作來實現一個web伺服器的示例,這個web伺服器用來處理各種客戶端請求。

準備工作

你應該事先閱讀第1章中建立和執行執行緒的指南,瞭解Java中執行緒建立的基本機制。你可以比較這兩種機制,根據問題選擇最好的一個。

這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。

如何做…

按以下步驟來實現的這個例子:

1.首先,實現能被伺服器執行的任務。建立實現Runnable介面的Task類。

public class Task implements Runnable {

2.宣告一個型別為Date,名為initDate的屬性,來儲存任務建立日期,和一個型別為String,名為name的屬性,來儲存任務的名稱。

private Date initDate;
private String name;

3.實現Task構造器,初始化這兩個屬性。

public Task(String name){
initDate=new Date();
this.name=name;
}

4.實現run()方法。

@Override
public void run() {

5.首先,將initDate屬性和實際日期(這是任務的開始日期)寫入到控制檯。

System.out.printf("%s: Task %s: Created on: %s\n",Thread.currentThread().getName(),name,initDate);
System.out.printf("%s: Task %s: Started on: %s\n",Thread.currentThread().getName(),name,new Date());

6.然後,使任務睡眠一個隨機時間。

try {
Long duration=(long)(Math.random()*10);
System.out.printf("%s: Task %s: Doing a task during %dseconds\n",Thread.currentThread().getName(),name,duration);
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}

7.最後,將任務完成時間寫入控制檯。

System.out.printf("%s: Task %s: Finished on: %s\n",Thread.currentThread().getName(),name,new Date());

8.現在,實現伺服器類,用來執行使用執行者接受的所有任務。建立一個Server類。

public class Server {

9.宣告一個型別為ThreadPoolExecutor,名為executor的屬性。

private ThreadPoolExecutor executor;

10.實現Server構造器,使用Executors類初始化ThreadPoolExecutor物件。

public Server(){
executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
}

11.實現executeTask()方法,接收Task物件作為引數並將其提交到執行者。首先,寫入一條資訊到控制檯,表明有一個新的任務到達。

public void executeTask(Task task){
System.out.printf("Server: A new task has arrived\n");

12.然後,呼叫執行者的execute()方法來提交這個任務。

executor.execute(task);

13.最後,將執行者的資料寫入到控制檯來看它們的狀態。

System.out.printf("Server: Pool Size: %d\n",executor.getPoolSize());
System.out.printf("Server: Active Count: %d\n",executor.getActiveCount());
System.out.printf("Server: Completed Tasks: %d\n",executor.getCompletedTaskCount());

14.實現endServer()方法,在這個方法中,呼叫執行者的shutdown()方法來結束任務執行。

public void endServer() {
executor.shutdown();
}

15.最後,實現這個示例的主類,建立Main類,並實現main()方法。

public class Main {
public static void main(String[] args) {
Server server=new Server();
for (int i=0; i<100; i++){
Task task=new Task("Task "+i);
server.executeTask(task);
}
server.endServer();
}
}

它是如何工作的…

Server類是這個示例的關鍵。它建立和使用ThreadPoolExecutor執行任務。

第一個重要點是在Server類的構造器中建立ThreadPoolExecutor。ThreadPoolExecutor有4個不同的構造器,但由於它 們的複雜性,Java併發API提供Executors類來構造執行者和其他相關物件。即使我們可以通過ThreadPoolExecutor類的任意一 個構造器來建立ThreadPoolExecutor,但這裡推薦使用Executors類。

在本例中,你已經使用 newCachedThreadPool()方法建立一個快取執行緒池。這個方法返回ExecutorService物件,所以它被轉換為 ThreadPoolExecutor型別來訪問它的所有方法。你已建立的快取執行緒池,當需要執行新的任務會建立新的執行緒,如果它們已經完成執行任務,變成可用狀態,會重新使用這些執行緒。執行緒重複利用的好處是,它減少執行緒建立的時間。快取執行緒池的缺點是,為新任務不斷建立執行緒, 所以如果你提交過多的任務給執行者,會使系統超載。

注意事項:使用通過newCachedThreadPool()方法建立的執行者,只有當你有一個合理的執行緒數或任務有一個很短的執行時間。

一旦你建立執行者,你可以使用execute()方法提交Runnable或Callable型別的任務。在本例中,你提交實現Runnable介面的Task類物件。

你也列印了一些關於執行者資訊的日誌資訊。特別地,你可以使用了以下方法:

  • getPoolSize():此方法返回執行緒池實際的執行緒數。
  • getActiveCount():此方法返回在執行者中正在執行任務的執行緒數。
  • getCompletedTaskCount():此方法返回執行者完成的任務數。

ThreadPoolExecutor 類和一般執行者的一個關鍵方面是,你必須明確地結束它。如果你沒有這麼做,這個執行者會繼續它的執行,並且這個程式不會結束。如果執行者沒有任務可執行, 它會繼續等待新任務並且不會結束它的執行。一個Java應用程式將不會結束,除非所有的非守護執行緒完成它們的執行。所以,如果你不結束這個執行者,你的應用程式將不會結束。

當執行者完成所有待處理的任務,你可以使用ThreadPoolExecutor類的shutdown()方法來表明你想要結束執行者。在你呼叫shutdown()方法之後,如果你試圖提交其他任務給執行者,它將會拒絕,並且丟擲RejectedExecutionException異常。

以下截圖展示了執行這個示例的一部分:

1

當最後的任務到達伺服器時,執行者擁有100個任務,97個活動執行緒的池。

不止這些…

ThreadPoolExecutor 類提供了許多獲取它狀態的方法,我們在這個示例中,使用getPoolSize()、getActiveCount()和 getCompletedTaskCount()方法來獲取執行者的池大小、執行緒數、完成任務數資訊。你也可以使用 getLargestPoolSize()方法,返回池中某一時刻最大的執行緒數。

ThreadPoolExecutor類也提供其他與結束執行者相關的方法,這些方法是:

  • shutdownNow():此方法立即關閉執行者。它不會執行待處理的任務,但是它會返回待處理任務的列表。當你呼叫這個方法時,正在執行的任務繼續它們的執行,但這個方法並不會等待它們的結束。
  • isTerminated():如果你已經呼叫shutdown()或shutdownNow()方法,並且執行者完成關閉它的處理時,此方法返回true。
  • isShutdown():如果你在執行者中呼叫shutdown()方法,此方法返回true。
  • awaitTermination(long timeout, TimeUnit unit):此方法阻塞呼叫執行緒,直到執行者的任務結束或超時。TimeUnit類是個列舉類,有如下常 量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。

注意事項:如果你想要等待任務的完成,不管它們的持續時間,請使用大的超時,如:DAYS。

參見

  • 在第4章,執行緒執行者的中執行者控制被拒絕任務指南
  • 在第8章,測試併發應用程式中的監控Executor framework指南