一篇文章帶你完全瞭解JAVA線程池,再也不用擔心被面試官問了

1.什麼是線程池

線程(thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位,我們的程序最終都是由線程進行運作。在Java中,創建和銷燬線程的動作是很消耗資源的,因此就出現了所謂“池化資源”技術。線程池是池化資源技術的一個應用,所謂線程池,顧名思義就是預先按某個規定創建若干個可執行線程放入一個容器中(線程池),需要使用的時候從線程池中去取,用完之後不銷燬而是放回去,從而減少了線程創建和銷燬的次數,達到節約資源的目的。

2.爲什麼要使用線程池

2.1 降低資源消耗

前面已經講到線程池的出現減少了線程創建和銷燬的次數,每個線程都可以被重複利用,可執行多個任務。

2.2 提高系統的響應速度

每當有任務到來時,直接複用線程池中的線程,而不需要等待新線程的創建,這個動作可以帶來響應速度的提升

2.3 防止過多的線程搞壞系統

可以根據系統的承受能力,調整線程池中的工作線程的數量,防止因爲線程過多服務器變慢或死機。java一個線程默認佔用空間爲1M,可以想象一旦手動創建線程過多極有可能導致內存溢出。

3.線程池主要參數

我們可以用Executors類來創建一些常用的線程池,但是像阿里是禁止直接通過Executors類直接創建線程池的,具體的原因稍後再談。

在瞭解Executors類所提供的幾個線程池前,我們首先來了解一下ThreadPoolExecutor的主要參數,ThreadPoolExecutor是創建線程池的類,我們選取參數最多的構造方法來看一下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)複製代碼

3.1 corePoolSize

當向線程池提交一個任務時,如果線程池中已創建的線程數量小於corePoolSIze,即便存在空閒線程,也會創建一個新線程來執行任務,直到創建的線程數大於或等於corePoolSIze。

3.2 maximumPoolSize

線程池所允許的最大線程個數,當隊列滿了且已經創建的線程數小於maximumPoolSize時,會創建新的線程執行任務。

3.3 keepAliveTime

當線程中的線程數大於corePoolSIze時,如果線程空閒時間大於keepAliveTime,該線程就會被銷燬。

3.4 unit

keepAliveTime的時間單位

3.5 workQueue

用於保存等待執行任務的隊列

3.6 threadFactory

用於創建新線程。threadFactory創建的線程也是採用new Thread()方式,threadFactory創建的線程名都具有統一的風格:pool-m-thread-n

3.7 handler

拒絕策略,當線程池和隊列滿了之後,再加入新線程後會執行此策略。

下面是四種線程池的拒絕策略:

AbortPolicy:中斷任務並拋出異常

DiscardPolicy:中段任務但是不拋出異常

DiscardOldestPolicy:丟棄隊列中最老的任務,然後嘗試提交新任務

CallerRunsPolicy:由調用線程處理該任務

4.線程池執行流程

當我們瞭解了ThreadPoolExecutor的七個參數後,我們就可以很快的理解線程池的流程:

當提交任務後,首先判斷當前線程數是否超過核心線程數,如果沒超過則創建新線程執行任務,否則判斷工作隊列是否已滿,如果未滿則將任務添加到隊列中,否則判斷線程數是否超過最大線程數,如果未超過則創建線程執行任務,否則執行拒絕策略。

5.Executors提供的線程池

executors提供了許多種線程池供用戶使用,雖然很多公司禁止使用executors創建線程池,但是對於剛開始解除線程池的人來說,Executors類所提供的線程池能很好的帶你進入多線程的世界。

5.1 newSingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor();複製代碼

聽名字就可以知道這是一個單線程的線程池,在這個線程池中只有一個線程在工作,相當於單線程執行所有任務,此線程可以保證所有任務的執行順序按照提交順序執行,看構造方法也可以看出,corePoolSize和maximumPoolSize都是1。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue()));
}複製代碼

5.2 newFixedThreadPool

ExecutorService executorService = Executors.newFixedThreadPool(2);複製代碼

固定長度的線程池,線程池的長度在創建時通過變量傳入。下面是newFixedThreadPool的構造方法,corePoolSize和maximumPoolSize都是傳入的參數值

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue());
}複製代碼

5.3 newCachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();複製代碼

可緩存線程池,這個線程池設定keepAliveTime爲60秒,並且對最大線程數量幾乎不做控制。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue());
}複製代碼

觀察構造方法,corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即線程數量幾乎無限制。設定keepAliveTime 爲60秒,線程空閒60秒後自動結束,因爲該線程池創建無限制,不會有隊列等待,所以使用SynchronousQueue同步隊列。

5.4 newScheduledThreadPool

創建一個定時的線程池。此線程池支持定時以及週期性執行任務的需求。下面是newScheduledThreadPool的用法:

Thread thread1=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread1");
    }
});
Thread thread2=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread2");
    }
});
Thread thread3=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread3");
    }
});
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
//在1000ms後執行thread1
scheduledExecutorService.schedule(thread1,1000,TimeUnit.MILLISECONDS);
//在1000ms後每隔1000ms執行一次thread2,如果任務執行時間比間隔時間長,則延遲執行
scheduledExecutorService.scheduleAtFixedRate(thread2,1000,1000,TimeUnit.MILLISECONDS);
//和第二種方式類似,但下一次任務開始的時間爲:上一次任務結束時間(而不是開始時間) + delay時間
scheduledExecutorService.scheduleWithFixedDelay(thread3,1000,1000,TimeUnit.MILLISECONDS);複製代碼

6.爲什麼阿里巴巴禁止程序員用Exectors創建線程池

如果你的idea裝了Alibaba Java Codeing Guidelines插件(推薦大家使用,有助於讓你的代碼更加規範),那麼當你寫了Exectors創建線程池後會看到提示:

並且阿里將這個用法定義爲Blocker,即不允許使用,而是讓人們用ThreadPoolExecutor的方式創建線程池。原因是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的人更加明確線程池的運行規則,規避資源耗盡的風險。

Executors返回的線程池對象的弊端如下:

1)FixedThreadPool和SingleThreadPool:

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

2)CachedThreadPool:

  允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。

下面是ThreadPoolExecutor創建線程池的簡單例子

int corePoolSize=5;
int maximumPoolSize=10;
long keepAliveTime=30;
BlockingQueue blockingQueue=new ArrayBlockingQueue(2);
RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, blockingQueue, handler);
threadPoolExecutor.execute(thread1);複製代碼

7.總結

關於本篇文章如果有任何錯誤、疑問或者補充歡迎大家留言或私信我。歡迎關注我的微信公衆號《魚仔ly》與我交流,每天進步一點點。

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