在Java中,如果每個請求到達就創建一個新的線程,創建和銷燬線程花費的世界和消耗的系統資源都相當大,甚至可能要比在實際的用戶請求的時間和資源要多的多。
如果在一個JVM裏創建太多線程,可能會使得系統由於過度消耗內存或切換過度而導致系統資源不足。
爲了解決這些問題,就有了線程池的該娘,線程池的核心邏輯就是提前創建好若干個線程放在一個容器中。如果有任務需要處理則將任務之間分配給線程池中的線程來執行即可,任務處理完以後,這個線程不會銷燬,而是等待後續分配任務。同時通過線程池來重複管理線程還可以避免創建大量線程增加開銷。
線程池的優點:降低創建線程和銷燬線程的性能開銷;提高響應速度,當有新任務需要執行是不需要等待線程創建就可以立馬執行;合理的設置線程池大小可以避免因爲線程數超過硬件資源瓶頸帶來的問題。
ThreadPoolExecutor
線程池技術在併發時會使用到,Java中的線程池的使用是通過調用ThreadPoolExecutor來實現的。
//----------------- 參數說明 ----------------- // new ThreadPoolExecutor(corePoolSize, //初始化線程數 // maximumPoolSize, //最大線程數 // keepAliveTime, //線程活躍時間 // unit, //線程活躍時間單位(秒、分) // workQueue, //任務隊列 // threadFactory, //創建新線程使用的工廠 // handler); //拒絕策略,如線程池滿了 |
有界隊列和無界隊列的選擇:
有界隊列:有任務需要執行,如果線程池實際線程數小於corePoolSize,則優先創建線程。
若大於CorePoolSize,則會將任務加入隊列中。
若隊列已滿,則在總線程數不大於maximumPoolSize的前提下創建新的線程池。
若線程數大於maximumPoolSize,則執行拒絕策略,或其他自定義方式。
有界隊列(LinkedBlockingQueue):與有界隊列相比,除非系統資源耗盡,否則無界隊列的任務隊列不存在任務入隊失敗的情況。
當有新的任務到來,系統線程數小於corePoolSize,則新建線程執行任務,在達到了corePoolSize後,就不會繼續增加。
若後續依然有新的任務加入,而沒有空間的線程資源,則任務進入隊列等待,若任務創建和處理速度差異很大,無界隊列會快速增長,直到耗盡系統內存。
無界隊列是以corePoolSize爲瓶頸,maxPoolSize沒有起作用的。
JDK自帶的拒絕策略:
AbortPolicy:直接拋出異常,系統正常工作。
CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。
DiscardOldestPolicy:對其最老的一個請求,嘗試再次提交當前任務。
DiscardPolicy:丟棄無法處理的任務,不給予如何處理。
自定義線程池拒絕策略:實現RejectedExecutionHandler接口。
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;
/** * */ public class P01ThreadPoolExecutor { public static void main(String[] args) throws InterruptedException {
/* * 有界隊列和無界隊列的選擇: * 有界隊列: * 有任務需要執行,如果線程池實際線程數小於corePoolSize,則優先創建線程。 * 若大於CorePoolSize,則會將任務加入隊列中。 * 若隊列已滿,則在總線程數不大於maximumPoolSize的前提下創建新的線程池。 * 若線程數大於maximumPoolSize,則執行拒絕策略,或其他自定義方式。 * * 有界隊列(LinkedBlockingQueue): * 與有界隊列相比,除非系統資源耗盡,否則無界隊列的任務隊列不存在任務入隊失敗的情況。 * 當有新的任務到來,系統線程數小於corePoolSize,則新建線程執行任務,在達到了corePoolSize後,就不會繼續增加。 * 若後續依然有新的任務加入,而沒有空間的線程資源,則任務進入隊列等待,若任務創建和處理速度差異很大,無界隊列會快速增長,直到耗盡系統內存。 * 無界隊列是以corePoolSize爲瓶頸,maxPoolSize沒有起作用的。 */
/* * JDK自帶的拒絕策略: * AbortPolicy:直接拋出異常,系統正常工作。 * CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。 * DiscardOldestPolicy:對其最老的一個請求,嘗試再次提交當前任務。 * DiscardPolicy:丟棄無法處理的任務,不給予如何處理。 * * 自定義線程池拒絕策略:實現RejectedExecutionHandler接口。 */
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, //初始化線程數 2, //最大線程數 30, //活躍時間 TimeUnit.SECONDS //活躍時間單位 ,new ArrayBlockingQueue<Runnable>(3) //有界隊列 // ,new LinkedBlockingQueue<Runnable>() //無界隊列 // ,new DefTestReject() //自定義拒絕策略 // ,new AbortPolicy() ); pool.execute(new TestTask(1, "A")); pool.execute(new TestTask(2, "B")); pool.execute(new TestTask(3, "C")); pool.execute(new TestTask(4, "D")); pool.execute(new TestTask(5, "E")); pool.execute(new TestTask(6, "F"));
Thread.sleep(1000);
System.out.println("隊列長度:" + pool.getQueue().size());
Thread.sleep(2000);
System.out.println("隊列長度:" + pool.getQueue().size());
//1、首次運行 //2、打開自定義拒絕策略,打開註釋pool.execute(new TestTask(6, "F"));,運行可以看到拒絕策略起作用了 //3、打開無界隊列,每次運行都是1個運行,而不是按照maximumPoolSize數量來運行 //4、打開自定義拒絕策略和無界隊列運行,會發現拒絕策略沒有起來。 //5、註釋自定義拒絕策略,打開JDK拒絕策略和有界隊列,會發現起作用了。
pool.shutdown();
} } class TestTask implements Runnable {
private int id;
private String name;
public TestTask(int id, String name) { super(); this.id = id; this.name = name; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public void run() { // System.out.println("當前id:" + this.id + ", " + this.name + ",開始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當前id:" + this.id + ", " + this.name + ",結束"); } } /** * 自定義線程池拒絕策略 */ class DefTestReject implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("任務加不進去線程池.........記錄下日誌,發送郵件/短信通知開發人員。"); } } |
Executors
爲了更好的控制多線程,JDK提供了一套線程礦機Executor,讓開發有效的進行線程控制。
這些類都是在java.util.concurrent包中,是JDK併發包中的核心,其中有一個比較重要的類Executors。
Executors扮演的是線程工廠的角色,在使用中通常也是使用Executors可以創建特定功能的線程池。
newFixedThreadPool():創建一個線程的線程池,線程池有空閒,則立即執行。線程池沒有空閒的情況下,則會暫緩在一個任務隊列中。
newSingleThreadExecutor():創建一個線程的線程池,線程池有空閒,則立即執行。線程池沒有空閒的情況下,則會暫緩在一個任務隊列中。
newCachedThreadPool():返回一個可根據實際情況調整線程個數的線程池,不限制最大線程數量(內存)。線程池有空閒,則立即執行。如果沒有任務則不創建線程。並且每一個空閒線程在60秒內自動回收。
newScheduledThreadPool():可以指定線程數量。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;
/** * */ public class P02Executors { public static void main(String[] args) { /* * newFixedThreadPool():是返回一個固定數量的線程池,該方法的線程數始終不變, * 但有一個任務提交時,線程池空閒,則立即執行;線程沒有空閒的情況下,則會暫緩在一個任務隊列中等待有空閒的線程去執行。 * * 底層源碼:new ThreadPoolExecutor(nThreads, nThreads, * 0L, TimeUnit.MILLISECONDS, * new LinkedBlockingQueue<Runnable>()); * */ ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
/* * newSingleThreadExecutor():創建一個線程的線程池,線程池有空閒,則立即執行。 * 線程池沒有空閒的情況下,則會暫緩在一個任務隊列中。 * * 底層源碼:new ThreadPoolExecutor(1, 1, * 0L, TimeUnit.MILLISECONDS, * new LinkedBlockingQueue<Runnable>()) */ ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
/* * newCachedThreadPool():返回一個可根據實際情況調整線程個數的線程池,不限制最大線程數量(內存)。 * 線程池有空閒,則立即執行。如果沒有任務則不創建線程。並且每一個空閒線程在60秒內自動回收。 * * 底層源碼:new ThreadPoolExecutor(0, Integer.MAX_VALUE, * 60L, TimeUnit.SECONDS, * new SynchronousQueue<Runnable>()); */ ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
/* * newScheduledThreadPool(1):可以指定線程的數量。 * * 底層源碼: * new ScheduledThreadPoolExecutor(corePoolSize); * super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue()); * class ScheduledThreadPoolExecutor extends ThreadPoolExecutor */ ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(1); //開始等待3秒後執行,一秒執行一次 newScheduledThreadPool.scheduleWithFixedDelay(new Thread(new Runnable() { @Override public void run() { System.out.println("run"); } }), 3, 1, TimeUnit.SECONDS);
/* * 方法區別: * submit():可以傳入實現Callable接口或Runnable接口實例對象,另外有返回值。 * execute():只能傳入Runnable接口,另外無返回值 * * submit()可以看Future設計模式博客文章。 * */ } } |
Executors的submit()和execute()方法的區別:
submit():可以傳入實現Callable接口或Runnable接口實例對象,另外有返回值。可以看Future設計模式博客文章。
execute():只能傳入Runnable接口,另外無返回值