多線程學習:線程池
什麽是線程池?線程池的好處?
線程池的概念:線程池就首先創建一些線程,它們的集合稱為線程池。使用線程池可以很好地提高性能,線程池在系統啟動時即創建大量空閑的線程,程序將一個任務傳給線程池,線程池就會啟動一條線程來執行這個任務,執行結束以後,該線程並不會死亡,而是再次返回線程池中成為空閑狀態,等待執行下一個任務。
線程池的好處:1、降低資源消耗,通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。2、提高響應速度,當任務到達時,任務可以不需要等到線程創建就能立即執行。3、提高線程的可管理性,線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,
Java中的ThreadPoolExecutor類:
java在JDK1.5後加入了java.util.concurrent包,Executor是java.util.concurrent一個接口。Executor最頂層的實現是ThreadPoolExecutor類。Executor工廠類提供newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其實也只是ThreadPoolExecutor的構造函數參數不同而已。通過傳入不同的參數,就可以構造出適用於不同應用場景下的線程池。下面對ThreadPoolExecutor構造中的的幾個參數講解一下。
corePoolSize:
maximumPoolSize: 線程池最大線程數,它表示在線程池中最多能創建多少個線程。
keepAliveTime: 表示線程沒有任務執行時最多保持多久時間會終止。
unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性。
TimeUnit.DAYS; //天 TimeUnit.HOURS; //小時 TimeUnit.MINUTES; //分鐘 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //納秒
workQueue:一個阻塞隊列,用來存儲等待執行的任務。
threadFactory:線程工廠,主要用來創建線程。
handler:表示當拒絕處理任務時的策略,有以下四種取值:
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重復此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
線程池的幾種創建方式:
1、newCachedThreadPool:創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
public static void main(String[] args) { ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int temp = i; newCachedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ",i:" + temp); } }); } }
結論:線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。
2、newFixedThreadPool:創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。
public static void main(String[] args) { ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; newFixedThreadPool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + ",i:" + temp); } }); } }
結論:因為線程池大小為3,每個任務輸出打印結果後sleep 2秒,所以每兩秒打印3個結果。
定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()(返回可用處理器的Java虛擬機的數量。)
3、newScheduledThreadPool:創建一個定長線程池,即使是空線程也會保留。支持定時及周期性任務執行。
public static void main(String[] args) { ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5); for (int i = 0; i < 10; i++) { final int temp = i; newScheduledThreadPool.schedule(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()+",i:" + temp); } }, 3, TimeUnit.SECONDS); } }
結論:表示延遲三秒執行。
4、newSingleThreadExecutor:創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; newSingleThreadExecutor.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+",index:" + index); try { Thread.sleep(200); } catch (Exception e) { // TODO: handle exception } } }); } }
結論:相當於順序執行各個任務
線程池的原理:
如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閑線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;如果隊列已經滿了,則在總線程數不大於maximumPoolSize的前提下,則創建新的線程如果當前線程池中的線程數目達到maximumPoolSize,則會采取任務拒絕策略進行處理;如果線程池中的線程數量大於 corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許為核心池中的線程設置存活時間,那麽核心池中的線程空閑時間超過keepAliveTime,線程也會被終止。
自定義線程池:
public static void main(String[] args) { // 核心線程數1,最大線程2,隊列長度為3 ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3)); // 任務一 創建線程 執行 executor.execute(new TaskThred("任務1")); // 任務二緩存到隊列中 復用同一個線程 executor.execute(new TaskThred("任務2")); // 任務三緩存到隊列中 復用同一個線程 executor.execute(new TaskThred("任務3")); // 任務四緩存到隊列中 復用同一個線程 executor.execute(new TaskThred("任務4")); // 平滑的關閉線程池。(如果還有未執行完的任務,就等待它們執行完)。 executor.shutdown(); } } class TaskThred implements Runnable { private String taskName; public TaskThred(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(Thread.currentThread().getName() + taskName); }
結論:可以看到第一個線程直接創建執行,後面的線程因為大於核心線程數,就放進緩存隊列裏面等待取出執行
public static void main(String[] args) { // 核心線程數1,最大線程2,隊列長度為3 ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3)); // 任務一 創建線程 執行 executor.execute(new TaskThred("任務1")); // 任務二緩存到隊列中 復用同一個個線程 executor.execute(new TaskThred("任務2")); // 任務三緩存到隊列中 復用同一個個線程 executor.execute(new TaskThred("任務3")); // 任務四緩存到隊列中 復用同一個個線程 executor.execute(new TaskThred("任務4")); //任務5 超過隊列,判斷是否大於最大線程數。 executor.execute(new TaskThred("任務5")); // 平滑的關閉線程池。(如果還有未執行完的任務,就等待它們執行完)。 executor.shutdown(); }
結論:可以看到任務5超過隊列之後但是沒有超過最大線程數。就從新創建線程執行。
public static void main(String[] args) { // 核心線程數1,最大線程2,隊列長度為3 ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3)); // 任務一 創建線程 執行 executor.execute(new TaskThred("任務1")); // 任務二緩存到隊列中 復用同一個個線程 executor.execute(new TaskThred("任務2")); // 任務三緩存到隊列中 復用同一個個線程 executor.execute(new TaskThred("任務3")); // 任務四緩存到隊列中 復用同一個個線程 executor.execute(new TaskThred("任務4")); //任務5 超過隊列,判斷是否大於最大線程數。 executor.execute(new TaskThred("任務5")); //任務6超過隊列,也超過最大核心線程數,會報錯,出現拒絕策略 executor.execute(new TaskThred("任務6")); // 平滑的關閉線程池。(如果還有未執行完的任務,就等待它們執行完)。 executor.shutdown(); }
結論:當超過隊列緩存和最大線程數的時候就會采取拒絕策略
多線程學習:線程池