執行緒執行者(二)建立一個執行緒執行者
宣告:本文是《 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異常。
以下截圖展示了執行這個示例的一部分:
當最後的任務到達伺服器時,執行者擁有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指南