多線程【線程池】

1、什麼是線程池

線程池:指在初始化一個多線程應用程序過程當中建立一個線程集合,而後在須要執行新的任務時重用這些線程而不是新建一個線程,java

一旦任務已經完成了,線程回到池子中並等待下一次分配任務。程序員

2、使用線程池的好處

1)控制最大並大數。緩存

2)下降資源消耗。經過重複利用已建立的線程來下降線程建立和銷燬形成的消耗。服務器

3)提升響應速度。當任務到達時,任務不須要等到線程建立,而是能夠直接使用線程池中的空閒線程。多線程

4)提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配、延時執行、調優和監控等。ide

3、涉及到的類和接口

經常使用的線程池接口和類都在 java.util.concurrent包下,大體爲:工具

Executor:線程池的頂級接口this

ExecutorService:線程池接口,可經過submit()方法提交任務代碼.net

ExecutorService接口的實現類最經常使用的爲如下兩個:線程

ThreadPoolExecutor
ScheduledThreadPoolExecutor

和 Array -> Arrays、Collection -> Collections 同樣,線程池的建立也是有工具類可使用的:

Executors工廠類:經過此類能夠建立一個線程池

4、線程池種類

在 JDK 8 之後,一共有 5 種線程池,分別爲:

固定線程數的線程池

只有一個線程的線程池

可根據任務數動態擴容線程數的線程池

可調度的線程池

具備搶佔式操做的線程池

這些線程池都能由 Executors 工具類來進行建立,分別對應如下方法:

1)newFixedThreadPool:建立指定的、固定個數的線程池
2)newCachedThreadPool:建立緩存線程池(線程個數根據任務數逐漸增長,上線爲 Integer.MAX_VALUE)
3)newSingleThreadExecutor:建立單個線程的線程池
4)newScheduledThreadPool:建立可調度的線程池 調度:定時、週期執行

5)newWorkStealingPool:建立具備搶佔式操做的線程池

對於 newWorkStealingPool 的補充:

newWorkStealingPool,這個是 JDK1.8 版本加入的一種線程池,stealing 翻譯爲搶斷、竊取的意思,它實現的一個線程池和上面4種都不同,用的是 ForkJoinPool 類。

newWorkStealingPool 適合使用在很耗時的操做,可是 newWorkStealingPool 不是 ThreadPoolExecutor 的擴展,它是新的線程池類 ForkJoinPool 的擴展,可是都是在統一的一個 Executors 類中實現,因爲可以合理的使用 CPU 進行任務操做(並行操做),因此適合使用在很耗時的任務中

參考文章:

https://blog.csdn.net/qq_38428623/article/details/86689800
https://blog.csdn.net/tjbsl/article/details/98480843

5、如何使用線程池

(一)使用步驟

1)建立線程池對象

2)建立線程任務

3)使用線程池對象的 submit() 或者 execute() 方法提交要執行的任務

4)使用完畢,可使用shutdown()方法關閉線程池

(二)案例代碼

需求:使用線程池管理線程來簡單的模擬買票程序。

public class Demo(){
    public static void main(String[] args) {
        test();
    }
    
    public static void test(){
        //一、建立線程池對象
        ExecutorService pool = Executors.newFixedThreadPool(4);

        //二、建立任務
        Runnable runnable = new Runnable(){
            private int tickets = 100;

            @Override
            public void run() {
                while (true){
                    if(tickets <= 0){
                        break;
                    }
                    System.out.println(Thread.currentThread().getName()+"賣了第"+tickets+"張票");
                    tickets--;
                }
            }
        };
        
        //三、將任務提交到線程池(須要幾個線程來執行就提交幾回)
        for (int i=0; i<5; i++){
            pool.submit(runnable);
		}
        
        //四、關閉線程池
        pool.shutdown();
}

補充:

shutdown:啓動有序關閉,其中先前提交的任務將被執行,但不會接受任何新任務
shutdownNow:嘗試中止全部正在執行的任務,中止等待任務的處理,並返回正在等待執行的任務列表。

execute() 和 submit() 的區別:

1)參數:execute 只能傳遞 Runnable;submit 既能夠傳遞 Runnable,也能夠傳遞 Callable

2)返回值:execute 沒有返回值;submit 有返回值,能夠獲取 Callable 的返回結果

6、線程池底層源碼查看

newFixedThreadPool

newCachedThreadPool

newSingleThreadExecutor

newScheduledThreadPool

7、線程池7大參數

ThreadPoolExecutor 的底層源碼:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

corePoolSize:線程池中的常駐核心線程數

maximumPoolSize:線程池中可以容納同時指向的最大線程數,此值必須大於等於1

keepAliveTime:多餘空閒線程的存活時間

(若線程池中當前線程數超過corePoolSize時,且空閒線程的空閒時間達到keepAliveTime時,多餘空閒線程會被銷燬,直到只剩下corePoolSize個線程爲止)

TimeUnit:keepAliveTime 的時間單位

workQueue:任務隊列,被提交但還沒有被執行的任務

ThreadFactory:線程工廠,用於建立線程,通常用默認的便可

RejectedExecutionHandler:拒絕策略,當任務太多來不及處理,如何拒絕任務

8、線程池底層工做原理

1)線程池剛建立時,裏面沒有一個線程。任務隊列是做爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會立刻執行它們。

2)當調用 execute() 方法添加一個任務時,線程池會作以下判斷:

​ a) 若是正在運行的線程數量小於 corePoolSize,那麼立刻建立線程運行這個任務;

​ b) 若是正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列;

​ c) 若是這時候隊列滿了,並且正在運行的線程數量小於 maximumPoolSize,那麼仍是要建立非核心線程馬上運行這個任務

​ d) 若是隊列滿了,並且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會執行拒絕策略。

3)當一個線程完成任務時,它會從隊列中取下一個任務來執行。

4)當一個線程無事可作,超過必定的時間(keepAliveTime)時,線程池會判斷,若是當前運行的線程數大於 corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize 的大小。

9、線程池的4大拒絕策略

線程池中的線程已經用完了,沒法繼續爲新任務服務,同時,等待隊列也已經排滿了,再也塞不下新任務了。這時候咱們就須要拒絕策略機制合理的處理這個問題。

JDK 內置的拒絕策略以下:

1)AbortPolicy : 直接拋出 RejectedExecutionException 異常,阻止系統正常運行。

2)CallerRunsPolicy : 該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用線程。

3)DiscardOldestPolicy : 丟棄隊列中等待最久的線程,而後把當前任務加入隊列中嘗試再次提交當前任務。

4)DiscardPolicy : 該策略默默地丟棄沒法處理的任務,不予任何處理。若是容許任務丟失,這是最好的一種方案。

以上內置拒絕策略均實現了 RejectedExecutionHandler 接口,若以上策略仍沒法知足實際須要,徹底能夠本身擴展 RejectedExecutionHandler 接口。

10、線程池的實際使用

經過查看 Executors 提供的默認線程池的底層源碼後,咱們會發現其有以下弊端:

1)FixedThreadPool 和 SingleThreadPool:

容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

容許的建立線程數量爲 Integer.MAX_VALUE,可能會堆積大量的線程,從而致使 OOM。

而且在《阿里巴巴Java開發手冊》中也有指出,線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式手動建立,這樣的處理方式能讓程序員更加明確線程池的容許規則,從而規避資源耗盡的風險。

小結:在實際開發中不會使用 Executors 建立,而是手動建立,本身指定參數。

11、線程池的手動建立

以上的參數是隨手寫的,實際開發中參數的設置要根據業務場景以及服務器配置來進行設置。

12、線程池配置合理線程數

設置線程池的參數時,須要從如下 2 個方面進行考慮:

系統是 CPU 密集型?

系統是 IO 密集型?

(一)CPU 密集型

CPU 密集的意思是該任務須要大量的運算,而沒有阻塞,CPU一直全速運行。

那麼這種狀況下,應該儘量配置少的線程數量,從而減小線程之間的切換,讓其充分利用時間進行計算。

通常公式爲:CPU核數 + 1 個線程的線程池。

能夠經過如下代碼來查看服務器的核數:

Runtime.getRuntime().availableProcessors()
(二)IO 密集型

IO 密集型的意思是該任務須要大量的 IO,即大量的阻塞。

那麼這種狀況下會致使有大量的 CPU 算力浪費在等待上,因此須要多配置線程數。

在 IO 密集型狀況下,瞭解到有兩種配置線程數的公式:

公式一:CPU核數/(1-阻塞係數),其中阻塞係數在 0.8-0.9 之間

如:8核CPU,能夠設置爲 8/(1-0.9)=80 個線程

公式二:CPU核數 * 2

線程數的設置參考文章:

http://mp.weixin.qq.com/s__biz=MzI5MzYzMDAwNw==&mid=2247488456&idx=4&sn=80ee015180d46f2bd5b26c166b7dab0a&chksm=ec6e6a90db19e3867f8ea9fd5da01c3378431d6dcf940eb9820c4ab917e05851471102515d17&scene=0&xtrack=1#rd

http://mp.weixin.qq.com/s__biz=MzU1MzUyMjYzNg==&mid=2247484319&idx=1&sn=6a22ad5e324562c900a66624239cc6eb&chksm=fbf0c73ccc874e2a5d0a9c9d8e030e426104a52247b11a0069662a3d41a775f8c5332aba3981&mpshare=1&scene=24&srcid=&sharer_sharetime=1591622673427&sharer_shareid=5d06ca706f31f4d058e964b8b7ccfcc9#rd

Java新手,如有錯誤,歡迎指正!

相關文章
相關標籤/搜索