Java多執行緒-ThreadPool執行緒池-2(四)
執行緒池是個神器,用得好會非常地方便。本來覺得執行緒池的構造器有些複雜,即使講清楚了對今後的用處可能也不太大,因為有一些Java定義好的執行緒池可以直接使用。但是(凡事總有個但是),還是覺得講一講可能跟有助於理解後面的常用執行緒池,所以該打臉還是打吧
因為直接結合程式碼看會更清楚一些,所以我把帶注釋的程式碼貼出來:
public class ThreadPoolExecutor { public ThreadPoolExecutor( /** * corePoolSize:初始化時指定的核心執行緒數,包括空閑執行緒,必須大於等於0,當有新任務提交時,會執行以下判斷(workCount為當前活躍的執行緒數量): * 當workCount< corePoolSize:即使執行緒池中有空閑執行緒,也會創建新執行緒 * 當corePoolSize ≤ workCount < maximumPoolSize:只有workQueue滿時才創建新執行緒 * 當corePoolSize < workCount < maximumPoolSize:且超過corePoolSize部分的執行緒空閑時間達到keepAliveTime時,就回收這些執行緒,當設置allowCoreThreadTimeOut(true)時, * 執行緒池中corePoolSize範圍內的執行緒空閑時間達到keepAliveTime也將被回收 * 當設置corePoolSize == maximumPoolSize:執行緒池的大小固定,此時如有新任務提交,且workQueue未滿時,會將請求放入workQueue,等待有空閑的執行緒從workQueue中取任務並處理 * 當workCount ≥ maximumPoolSize:若workQueue滿,則採取handler對應的策略 */ int corePoolSize, // maximumPoolSize:初始化時指定的最大執行緒數量 int maximumPoolSize, // keepAliveTime:執行緒池維護執行緒所允許的空閑時間。當執行緒池中的執行緒數量大於corePoolSize時,如果這時沒有新的任務提交,核心執行緒外的執行緒不會立即銷毀,而是等待,直到等待的時間超過了keepAliveTime long keepAliveTime, // 空閑時間單位 TimeUnit unit, /** * workQueue:阻塞隊列的類型是保存等待執行的任務的阻塞隊列,主要有四種提交方式: * SynchronousQueue:同步隊列,這個「隊列」內部只包含了一個元素,隊列的size始終為0,每執行一個put,就需要一個take來解除阻塞,反之也一樣。飽和狀態下,執行緒池能處理的最大執行緒數量為maximumPoolSize * 使用SynchronousQueue隊列,提交的任務不會保存,而是會馬上提交執行 * 需要對程式的並發量有個準確的評估,才能設置合適的maximumPoolSize數量,否則很容易就會執行拒絕策略 * ArrayBlockingQueue:有界任務隊列,飽和狀態下,執行緒池能處理的最大執行緒數量為maximumPoolSize + ArrayBlockingQueue.SIZE * LinkedBlockingQueue:無界任務隊列,執行緒池的任務隊列可以無限制的添加新的任務,此時執行緒池能夠創建的最大執行緒數是corePoolSize, * 而maximumPoolSize就無效了,執行緒池飽和狀態下能處理的最大執行緒數量只取決於系統的性能 * PriorityBlockingQueue:優先任務隊列,同LinkedBlockingQueue一樣,它也是一個無界的任務隊列,只不過需要自己實現元素的Comparable排序介面 */ BlockingQueue<Runnable> workQueue, // threadFactory:創建新執行緒,使新創建的執行緒有相同的優先順序且為非守護執行緒,同時設置執行緒的名稱,默認使用Executors.DefaultThreadFactory類創建 ThreadFactory threadFactory, /** * handler:表示執行緒池的飽和策略,意思就是如果阻塞隊列滿了並且沒有空閑的執行緒,此時如果繼續提交任務,就需要採取一種策略處理該任務,執行緒池提供了4種策略 * AbortPolicy:直接拋出異常,這是默認策略 * CallerRunsPolicy:如果執行緒池的執行緒數量達到上限,則把任務隊列中的任務放在調用者的執行緒當運行 * DiscardOldestPolicy:丟棄阻塞隊列中靠最前的任務,並執行當前任務 * DiscardPolicy:直接丟棄任務 */ RejectedExecutionHandler handler) { // balabala… … } }
這樣就清晰多了。
其中,最主要是要清楚幾種workQueue,也就是BlockingQueue<Runnable>的作用。
SynchronousQueue同步隊列,這個隊列沒有所謂的緩衝,這樣做是為了排除阻塞時隊列丟消息的可能。如果沒有其他微服務並行執行的話,可以放心地用這個隊列,不然還是小心一點為妙。它的示例程式碼:
/** * 同步隊列 */ public class SynchronousQueueTest { public static void main(String[] args) { ExecutorService service = new ThreadPoolExecutor( 1, // 當要處理的執行緒數超過maximumPoolSize時,拋出異常 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); for (int i = 0; i < 10; i++) { service.execute(() -> System.out.println("當前執行緒 " + Thread.currentThread().getName()) ); } service.shutdown(); } }
ArrayBlockingQueue,它的使用範圍非常廣,一般可以用於輕量級的同步鎖,也就是在同一個服務中(也就是非微服務架構),如果要具有分散式鎖的功能又不想部署zookeeper這麼麻煩的話,ArrayBlockingQueue就是一個非常不錯的選擇。
/** * 有界阻塞隊列 */ public class ArrayBlockingQueueTest { public static void main(String[] args) { ExecutorService service = new ThreadPoolExecutor( // 要處理的執行緒數超過maximumPoolSize + workQueue.SIZE時,拋出異常 1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); // 因為 maximumPoolSize(2) + ArrayBlockingQueue.SIZE(10) < 13,所以會拋出異常 for (int i = 0; i < 13; i++) { service.execute(() -> System.out.println("當前執行緒 " + Thread.currentThread().getName()) ); } service.shutdown(); } }
再來看看ArrayBlockingQueue的另一個例子,可以加深印象:
public class ArrayBlockingQueueTester { public static BlockingQueue<String> queue = new ArrayBlockingQueue<String>(5); // 一個往裡放 class Producer implements Runnable { @Override public void run() { try { queue.put("川菜"); System.out.println(Thread.currentThread().getName() + " 廚師做好 川菜"); } catch (InterruptedException e) { e.printStackTrace(); } } } // 一個往外拿 class Consumer implements Runnable { @Override public void run() { try { String food = queue.take(); System.out.println( Thread.currentThread().getName() + " 客人消費 " + food ); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { // 客人等著菜 for (int i = 0; i < 5; i++) { new Thread(new ArrayBlockingQueueTester().new Consumer()).start(); } // 廚師做好菜 for (int i = 0; i < 5; i++) { new Thread(new ArrayBlockingQueueTester().new Producer()).start(); } } }
ArrayBlockingQueue說白了就是一個往裡放,一個往外拿:
1、往裡放的,只能最多放指定個數就不能再放了(阻塞,等待,這裡是5個);
2、往外拿的,如果沒有可以拿的了,就等著(阻塞,等待)。
咱們點菜的時候不就是這樣嗎?
LinkedBlockingQueue這個就牛逼了,相當於無底洞,有多少處理多少,此時執行緒池能夠創建的最大執行緒數是corePoolSize,而maximumPoolSize就成了擺設,這等於說是完全取決於系統的性能。
/** * 無界阻塞隊列 */ public class LinkedBlockingQueueTest { public static void main(String[] args) { // 要處理的執行緒數過大時,是否拋出異常,取決於機器的性能 ExecutorService service = new ThreadPoolExecutor( 1, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); for (int i = 0; i < 10000; i++) { service.execute(() -> System.out.println("當前執行緒 " + Thread.currentThread().getName()) ); } service.shutdown(); } }
最後一個隊列是PriorityBlockingQueue,它是一種有優先順序的無界阻塞隊列,默認的元素執行順序是升序,可以通過自定義介面Comparable<T>實現compareTo()方法來指定隊列中的元素執行順序。
/** * 測試類 */ public class Test1 implements Runnable, Comparable<Test1> { private int priority; public Test1(int priority) { this.priority = priority; } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; } @Override public int compareTo(Test1 o) { // 返回1時為升序 // 返回-1為降序 return this.priority > o.priority ? -1 : 1; } @Override public void run() { System.out.println( "當前執行緒 " + Thread.currentThread().getName() + ", priority = " + this.priority ); } } /** * 有優先順序的無界阻塞隊列 */ public class PriorityBlockingQueueTest { public static void main(String[] args) { ExecutorService service = new ThreadPoolExecutor( 1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); for (int i = 0; i < 10; i++) { service.execute(new Test1(i)); } service.shutdown(); } }
如果想在執行緒池的執行執行緒中加入一點自己希望的動作,可以通過自定義ThreadFactory實現。
/** * 測試類 */ public class Test2 implements Runnable { private String name; public Test2(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void run() { System.out.println(this.getName() + ",當前執行緒 " + Thread.currentThread().getName()); } } /** * 自定義ThreadFactory */ public class SelfDefineThreadPoolExecutor { public static void main(String[] args) { ExecutorService service = new ThreadPoolExecutor( 1, 2, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(8), // 自定義ThreadFactory new ThreadFactory() { @Override public Thread newThread(Runnable r) { System.out.println("執行緒 " + r.hashCode() + " 創建"); return new Thread(r, "thread-pool-" + r.hashCode()); } }, // 加入自定義動作 new ThreadPoolExecutor.CallerRunsPolicy() ) { public void beforeExecute(Thread thread, Runnable runnable) { System.out.println(((Test2) runnable).getName() + " 準備執行"); } public void afterExecute(Thread thread, Runnable runnable) { System.out.println(((Test2) runnable).getName() + " 執行完畢"); } public void terminated() { System.out.println("執行緒池關閉"); } }; for (int i = 0; i < 10; i++) { service.execute(new Test2("Test2" + i)); } service.shutdown(); } }
其實主要是把常用那幾個workQueue搞搞清楚,因為這幾個在今後的工作中可能會用到,尤其是ArrayBlockingQueue,它和後面會說的另兩個神器,可以說是是「執行緒三寶」。

