1. 程式人生 > >執行緒池系列一:執行緒池作用及Executors方法講解

執行緒池系列一:執行緒池作用及Executors方法講解

執行緒池的作用:

     執行緒池作用就是限制系統中執行執行緒的數量。
     根據系統的環境情況,可以自動或手動設定執行緒數量,達到執行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用執行緒池控制執行緒數量,其他執行緒 排隊等候。一個任務執行完畢,再從佇列的中取最前面的任務開始執行。若佇列中沒有等待程序,執行緒池的這一資源處於等待。當一個新任務需要執行時,如果執行緒 池中有等待的工作執行緒,就可以開始運行了;否則進入等待佇列。

為什麼要用執行緒池:

  1. 減少了建立和銷燬執行緒的次數,每個工作執行緒都可以被重複利用,可執行多個任務
  2. 可以根據系統的承受能力,調整執行緒池中工作線執行緒的數目,防止因為因為消耗過多的記憶體,而把伺服器累趴下(每個執行緒需要大約1MB記憶體,執行緒開的越多,消耗的記憶體也就越大,最後宕機)

ThreadGroup與ThreadPoolExecutor的區別

我自己的理解也是一直以為ThreadGroup就是ThreadPoolExecutor(執行緒池),這是一個非常大的誤會,最近把兩者仔細分析了下。 執行緒組表示一個執行緒的集合。此外,執行緒組也可以包含其他執行緒組。執行緒組構成一棵樹,在樹中,除了初始執行緒組外,每個執行緒組都有一個父執行緒組。允許執行緒訪問 有關自己的執行緒組的資訊,但是不允許它訪問有關其執行緒組的父執行緒組或其他任何執行緒組的資訊;執行緒消耗包括記憶體和其它系統資源在內的大量資源。除了 Thread 物件所需的記憶體之外,每個執行緒都需要兩個可能很大的執行呼叫堆疊。除此以外,JVM 可能會為每個

Java 執行緒建立一個本機執行緒,這些本機執行緒將消耗額外的系統資源。最後,雖然執行緒之間切換的排程開銷很小,但如果有很多執行緒,環境切換也可能嚴重地影響程式的性 能。執行緒池是因為執行緒的生成關閉很浪費資源 所以不要頻繁的操作 執行緒次 就是管理執行緒的地方 不用了它可以讓它休眠也就是他替你管理執行緒 而且比你管理的要好的多。執行緒池為執行緒生命週期開銷問題和資源不足問題提供瞭解決方案。通過對多個任務重用執行緒,執行緒建立的開銷被分攤到了多個任務上。其 好處是,因為在請求到達時執行緒已經存在,所以無意中也消除了執行緒建立所帶來的延遲。這樣,就可以立即為請求服務,使應用程式響應更快。而且,通過適當地調 整執行緒池中的執行緒數目,也就是當請求的數目超過某個閾值時,就強制其它任何新到的請求一直等待,直到獲得一個執行緒來處理為止,從而可以防止資源不足。

Executor詳解:

Java裡面執行緒池的頂級介面是Executor,但是嚴格意義上講Executor並不是一個執行緒池,而只是一個執行執行緒的工具。真正的執行緒池介面是ExecutorService。ThreadPoolExecutor是Executors類的底層實現。我們先介紹下Executors。

Sun在Java5中,對Java執行緒的類庫做了大量的擴充套件,其中執行緒池就是Java5的新特徵之一,除了執行緒池之外,還有很多多執行緒相關的內容,為多執行緒的程式設計帶來了極大便利。為了編寫高效穩定可靠的多執行緒程式,執行緒部分的新增內容顯得尤為重要。 

  有關Java5執行緒新特徵的內容全部在java.util.concurrent下面,裡面包含數目眾多的介面和類,熟悉這部分API特徵是一項艱難的學習過程。目前有關這方面的資料和書籍都少之又少,大所屬介紹執行緒方面書籍還停留在java5之前的知識層面上。 

  當然新特徵對做多執行緒程式沒有必須的關係,在java5之前通用可以寫出很優秀的多執行緒程式。只是代價不一樣而已。 

  執行緒池的基本思想還是一種物件池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的執行緒,池中執行緒執行排程由池管理器來處理。當有執行緒任務時,從池中取一個,執行完成後執行緒物件歸池,這樣可以避免反覆建立執行緒物件所帶來的效能開銷,節省了系統的資源。 

  在Java5之前,要實現一個執行緒池是相當有難度的,現在Java5為我們做好了一切,我們只需要按照提供的API來使用,即可享受執行緒池帶來的極大便利。 

  Java5的執行緒池分好多種:固定尺寸的執行緒池、可變尺寸連線池。 

  在使用執行緒池之前,必須知道如何去建立一個執行緒池,在Java5中,需要了解的是java.util.concurrent.Executors類的API,這個類提供大量建立連線池的靜態方法,是必須掌握的。

例項:

一、固定大小的執行緒池 

Java程式碼  

  1. import java.util.concurrent.Executors;  
  2. import java.util.concurrent.ExecutorService;  
  3. /**
  4.   * Java執行緒:執行緒池-
  5.   *
  6.   * @author Administrator 2009-11-4 23:30:44
  7.   */
  8. publicclass Test {  
  9. publicstaticvoid main(String[] args) {  
  10. //建立一個可重用固定執行緒數的執行緒池
  11.   ExecutorService pool = Executors.newFixedThreadPool(2);  
  12. //建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面
  13.   Thread t1 = new MyThread();  
  14.   Thread t2 = new MyThread();  
  15.   Thread t3 = new MyThread();  
  16.   Thread t4 = new MyThread();  
  17.   Thread t5 = new MyThread();  
  18. //將執行緒放入池中進行執行
  19.   pool.execute(t1);  
  20.   pool.execute(t2);  
  21.   pool.execute(t3);  
  22.   pool.execute(t4);  
  23.   pool.execute(t5);  
  24. //關閉執行緒池
  25.   pool.shutdown();  
  26.   }  
  27.   }  
  28. class MyThread extends Thread{  
  29. @Override
  30. publicvoid run() {  
  31.   System.out.println(Thread.currentThread().getName()+"正在執行。。。");  
  32.   }  
  33.   }   

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-2正在執行。。。 

  Process finished with exit code 0 

  二、單任務執行緒池 

  在上例的基礎上改一行建立pool物件的程式碼為: 

  //建立一個使用單個 worker 執行緒的 Executor,以無界佇列方式來執行該執行緒。 

Java程式碼  

  1. ExecutorService pool = Executors.newSingleThreadExecutor();   

  輸出結果為: 

      pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  Process finished with exit code 0 

  對於以上兩種連線池,大小都是固定的,當要加入的池的執行緒(或者任務)超過池最大尺寸時候,則入此執行緒池需要排隊等待。 

  一旦池中有執行緒完畢,則排隊等待的某個執行緒會入池執行。 

  三、可變尺寸的執行緒池 

  與上面的類似,只是改動下pool的建立方式: 

  //建立一個可根據需要建立新執行緒的執行緒池,但是在以前構造的執行緒可用時將重用它們。 

Java程式碼  

  1. ExecutorService pool = Executors.newCachedThreadPool();   

  pool-1-thread-5正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-4正在執行。。。 

  pool-1-thread-3正在執行。。。 

  pool-1-thread-2正在執行。。。 

  Process finished with exit code 0 

  四、延遲連線池

Java程式碼  

  1. import java.util.concurrent.Executors;  
  2. import java.util.concurrent.ScheduledExecutorService;  
  3. import java.util.concurrent.TimeUnit;  
  4. /**
  5.   * Java執行緒:執行緒池-
  6.   *
  7.   * @author Administrator 2009-11-4 23:30:44
  8.   */
  9. publicclass Test {  
  10. publicstaticvoid main(String[] args) {  
  11. //建立一個執行緒池,它可安排在給定延遲後執行命令或者定期地執行。
  12.   ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);  
  13. //建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面
  14.   Thread t1 = new MyThread();  
  15.   Thread t2 = new MyThread();  
  16.   Thread t3 = new MyThread();  
  17.   Thread t4 = new MyThread();  
  18.   Thread t5 = new MyThread();  
  19. //將執行緒放入池中進行執行
  20.   pool.execute(t1);  
  21.   pool.execute(t2);  
  22.   pool.execute(t3);  
  23. //使用延遲執行風格的方法
  24.   pool.schedule(t4, 10, TimeUnit.MILLISECONDS);  
  25.   pool.schedule(t5, 10, TimeUnit.MILLISECONDS);  
  26. //關閉執行緒池
  27.   pool.shutdown();  
  28.   }  
  29.   }  
  30. class MyThread extends Thread {  
  31. @Override
  32. publicvoid run() {  
  33.   System.out.println(Thread.currentThread().getName() + "正在執行。。。");  
  34.   }  
  35.   }   

    pool-1-thread-1正在執行。。。 

  pool-1-thread-2正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-2正在執行。。。 

  Process finished with exit code 0 

  五、單任務延遲連線池 

  在四程式碼基礎上,做改動 

  //建立一個單執行緒執行程式,它可安排在給定延遲後執行命令或者定期地執行。 

Java程式碼  

  1. ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();   

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-1正在執行。。。 

  Process finished with exit code 0 

  六、自定義執行緒池 

Java程式碼  

  1. import java.util.concurrent.ArrayBlockingQueue;  
  2. import java.util.concurrent.BlockingQueue;  
  3. import java.util.concurrent.ThreadPoolExecutor;  
  4. import java.util.concurrent.TimeUnit;  
  5. /**
  6.   * Java執行緒:執行緒池-自定義執行緒池
  7.   *
  8.   * @author Administrator 2009-11-4 23:30:44
  9.   */
  10. publicclass Test {  
  11. publicstaticvoid main(String[] args) {  
  12. //建立等待佇列
  13.   BlockingQueue bqueue = new ArrayBlockingQueue(20);  
  14. //建立一個單執行緒執行程式,它可安排在給定延遲後執行命令或者定期地執行。
  15.   ThreadPoolExecutor pool = new ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue);  
  16. //建立實現了Runnable介面物件,Thread物件當然也實現了Runnable介面
  17.   Thread t1 = new MyThread();  
  18.   Thread t2 = new MyThread();  
  19.   Thread t3 = new MyThread();  
  20.   Thread t4 = new MyThread();  
  21.   Thread t5 = new MyThread();  
  22.   Thread t6 = new MyThread();  
  23.   Thread t7 = new MyThread();  
  24. //將執行緒放入池中進行執行
  25.   pool.execute(t1);  
  26.   pool.execute(t2);  
  27.   pool.execute(t3);  
  28.   pool.execute(t4);  
  29.   pool.execute(t5);  
  30.   pool.execute(t6);  
  31.   pool.execute(t7);  
  32. //關閉執行緒池
  33.   pool.shutdown();  
  34.   }  
  35.   }  
  36. class MyThread extends Thread {  
  37. @Override
  38. publicvoid run() {  
  39.   System.out.println(Thread.currentThread().getName() + "正在執行。。。");  
  40. try {  
  41.   Thread.sleep(100L);  
  42.   } catch (InterruptedException e) {  
  43.   e.printStackTrace();  
  44.   }  
  45.   }  
  46.   }   

      pool-1-thread-1正在執行。。。 

  pool-1-thread-2正在執行。。。 

  pool-1-thread-2正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-2正在執行。。。 

  pool-1-thread-1正在執行。。。 

  pool-1-thread-2正在執行。。。 

  Process finished with exit code 0 

  建立自定義執行緒池的構造方法很多,本例中引數的含義如下: 

    ThreadPoolExecutor 

  public ThreadPoolExecutor(int corePoolSize, 

  int maximumPoolSize, 

  long keepAliveTime, 

  TimeUnit unit, 

  BlockingQueue workQueue) 

  用給定的初始引數和預設的執行緒工廠及處理程式建立新的 ThreadPoolExecutor。使用 Executors 工廠方法之一比使用此通用構造方法方便得多。 

  引數: 

  corePoolSize - 池中所儲存的執行緒數,包括空閒執行緒。 

  maximumPoolSize - 池中允許的最大執行緒數。 

  keepAliveTime - 當執行緒數大於核心時,此為終止前多餘的空閒執行緒等待新任務的最長時間。 

  unit - keepAliveTime 引數的時間單位。 

  workQueue - 執行前用於保持任務的佇列。此佇列僅保持由 execute 方法提交的 Runnable 任務。 

  丟擲: 

  IllegalArgumentException - 如果 corePoolSize 或 keepAliveTime 小於零,或者 maximumPoolSize 小於或等於零,或者 corePoolSize 大於 maximumPoolSize。 

  NullPointerException - 如果 workQueue 為 null 

  自定義連線池稍微麻煩些,不過通過建立的ThreadPoolExecutor執行緒池物件,可以獲取到當前執行緒池的尺寸、正在執行任務的執行緒數、工作佇列等等。