Java多線程零起步(三)

一、爲什麼要用線程池? 

1.減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。 

2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。 

Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService

執行一個異步任務你還只是如下new Thread嗎? 
那你就out太多了,new Thread的弊端如下: 
1.每次new Thread新建對象性能差。 
2.線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能佔用過多系統資源導致死機或oom。 
3.缺乏更多功能,如定時執行、定期執行、線程中斷。 
相比new Thread,Java提供的四種線程池的好處在於: 
1.重用存在的線程,減少對象創建、消亡的開銷,性能佳。 
2.可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。 
3.提供定時執行、定期執行、單線程、併發數控制等功能。

 

二、Executor的框架圖

接口:Executor,CompletionService,ExecutorService,ScheduledExecutorService

抽象類:AbstractExecutorService

實現類:ExecutorCompletionService,ThreadPoolExecutor,ScheduledThreadPoolExecutor

ExecutorService
真正的線程池接口。

ScheduledExecutorService 
能和Timer/TimerTask類似,解決那些需要任務重複執行的問題。

ThreadPoolExecutor 
ExecutorService的默認實現。

ScheduledThreadPoolExecutor 
繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,週期性任務調度的類實現

 

三、Executors提供四種線程池

newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。

newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行

 

創建四種線程池方式:https://www.cnblogs.com/lanseyitai1224/p/7895652.html

 

四、ThreadPoolExecutor

ThreadPoolExecutor是ExecutorService的一個實現類,它使用可能的幾個池線程之一執行每個提交的任務,通常使用 Executors 工廠方法配置。

ThreadPoolExecutor構造函數:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    } 

除了ScheduledThreadPool,Executors都是通過該構造方法來創建其他三種不同的線程池的

corePoolSize: 線程池維護線程的最少數量 

如果執行了線程池的prestartAllCoreThreads()方法,線程池會提前創建並啓動所有核心線程。

maximumPoolSize:線程池允許的最大線程數,他表示最大能創建多少個線程。maximumPoolSize肯定是大於等於corePoolSize

keepAliveTime: 表示線程沒有任務時最多保持多久然後停止。默認情況下,只有線程池中線程數大於corePoolSize 時,keepAliveTime 纔會起作用。換句話說,當線程池中的線程數大於corePoolSize,並且一個線程空閒時間達到了keepAliveTime,那麼就是shutdown。 

unit: 線程池維護線程所允許的空閒時間的單位 ,keepAliveTime 的單位。

workQueue: 線程池所使用的緩衝隊列 

一個阻塞隊列,用來存儲等待執行的任務,當線程池中的線程數超過它的corePoolSize的時候,線程會進入阻塞隊列進行阻塞等待。通過workQueue,線程池實現了阻塞功能

handler: 線程池對拒絕任務的處理策略 

threadFactory :線程工廠,用來創建線程。

一個任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是 Runnable類型對象的run()方法。 

 

當一個任務通過execute(Runnable)方法欲添加到線程池時: 

如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。 

如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。 

如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。 

如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。 

也就是:處理任務的優先級爲: 

核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。 

當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。 

unit可選的參數爲java.util.concurrent.TimeUnit中的幾個靜態屬性: 

NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS。 

workQueue我常用的是:java.util.concurrent.ArrayBlockingQueue 

 

五、線程池四種拒絕策略

  • AbortPolicy:丟棄任務並拋出RejectedExecutionException
  • CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交線程的性能極有可能會急劇下降。
  • DiscardOldestPolicy:丟棄隊列中最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務。
  • DiscardPolicy:丟棄任務,不做任何處理

 

六、線程池的任務處理策略

如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;

如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;

如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。

 

七、線程池的關閉

ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:

shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務

shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務

 

八、如何選擇線程池數量

線程池的大小決定着系統的性能,過大或者過小的線程池數量都無法發揮最優的系統性能。

當然線程池的大小也不需要做的太過於精確,只需要避免過大和過小的情況。一般來說,確定線程池的大小需要考慮CPU的數量,內存大小,任務是計算密集型還是IO密集型等因素

NCPU = CPU的數量

UCPU = 期望對CPU的使用率 0 ≤ UCPU ≤ 1

W/C = 等待時間與計算時間的比率

如果希望處理器達到理想的使用率,那麼線程池的最優大小爲:

線程池大小=NCPU *UCPU(1+W/C)

 

引用:

https://www.cnblogs.com/superfj/p/7544971.html

https://blog.csdn.net/linghu_java/article/details/17123057

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章