線程池基礎(chǔ)知識(shí)整理

本文為 Crocutax 原創(chuàng) , 轉(zhuǎn)載請(qǐng)注明出處 http://www.crocutax.com

1.為什么需要線程池

在面向?qū)ο缶幊讨?,創(chuàng)建和銷(xiāo)毀對(duì)象是很耗時(shí)的,因?yàn)閯?chuàng)建一個(gè)對(duì)象要獲取內(nèi)存資源或者其他更多資源.所以在日常編程中才會(huì)有意的避免過(guò)多的創(chuàng)建并不必要的對(duì)象.

線程的創(chuàng)建和銷(xiāo)毀也是同樣,而且相比于普通的對(duì)象更為消耗資源.線程池技術(shù)的引入,就是為了解決這一問(wèn)題.

1.1 線程池簡(jiǎn)介

線程池是指在初始化一個(gè)多線程應(yīng)用程序過(guò)程中創(chuàng)建的一個(gè)線程集合,線程池在任務(wù)未到來(lái)之前,會(huì)創(chuàng)建一定數(shù)量的線程放入空閑隊(duì)列中.這些線程都是處于睡眠狀態(tài),即均未啟動(dòng),因此不消耗CPU,只是占用很小的內(nèi)存空間.當(dāng)請(qǐng)求到來(lái)之后,線程池給這次請(qǐng)求分配一個(gè)空閑線程,把請(qǐng)求傳入此線程中運(yùn)行,進(jìn)行處理.

當(dāng)預(yù)先創(chuàng)建的線程都處于運(yùn)行狀態(tài)時(shí),線程池可以再創(chuàng)建一定數(shù)量的新線程,用于處理更多的任務(wù)請(qǐng)求.

如果線程池中的最大線程數(shù)使用滿(mǎn)了,則會(huì)拋出異常,拒絕請(qǐng)求.當(dāng)系統(tǒng)比較清閑時(shí),也可以通過(guò)移除一部分一直處于停用狀態(tài)的線程,線程池中的每個(gè)線程都有可能被分配多個(gè)任務(wù),一旦任務(wù)完成,線程回到線程池中并等待下一次分配任務(wù).

使用線程池可以提升性能,減少CPU資源的消耗,同時(shí)還可以控制活動(dòng)線程,防止并發(fā)線程過(guò)多,避免內(nèi)存消耗過(guò)度.

1.2 線程池優(yōu)點(diǎn)總結(jié)

  • 復(fù)用線程池中的線程,避免因?yàn)榫€程的創(chuàng)建和銷(xiāo)毀所帶來(lái)的性能開(kāi)銷(xiāo)
  • 能有效控制線程池的最大并發(fā)數(shù)量,避免大量線程之間因互相搶占系統(tǒng)資源而導(dǎo)致的阻塞現(xiàn)象.
  • 能夠?qū)€程進(jìn)行簡(jiǎn)單的管理,并提供定時(shí)執(zhí)行,指定間隔,循環(huán)執(zhí)行等功能.

2.Executor的繼承關(guān)系圖

黃色為接口 , 藍(lán)色為類(lèi)

Executor繼承關(guān)系圖.png

體系成員簡(jiǎn)介:

  • Executor
    線程池體系的頂層接口,只有一個(gè)execute()方法用于執(zhí)行Runnable任務(wù).該體系內(nèi)的所有類(lèi),接口都默認(rèn)實(shí)現(xiàn)/繼承 此接口,并在此基礎(chǔ)上進(jìn)行分類(lèi)擴(kuò)展.
//源碼
public interface Executor {

    /**
     * @param command Runnable任務(wù)
     * @throws RejectedExecutionException 如果任務(wù)無(wú)法繼續(xù)執(zhí)行,則拋出此異常
     * @throws NullPointerException 如果傳入的Runnable為null,則拋出此異常
     */
    void execute(Runnable command);
}
  • ExecutorService
    Executor的擴(kuò)展接口,用于定義一些Runnable管理相關(guān)的方法,比如

    • void shutdown(); 有序關(guān)閉已經(jīng)提交的任務(wù),但是不再接受新的任務(wù),重復(fù)shotdown無(wú)效.而且此方法不會(huì)等待已提交任務(wù)的執(zhí)行完畢.
    • List<Runnable> shutdownNow(); 嘗試停止所有正在執(zhí)行的任務(wù),終止所有處于等待隊(duì)列中的任務(wù),并將這些等待被執(zhí)行的任務(wù)返回給調(diào)用者
    • boolean isShutdown(); 判斷線程池是否已關(guān)閉
    • boolean isTerminated(); 當(dāng)調(diào)用了showdown()方法后,所有任務(wù)是否已執(zhí)行結(jié)束.注意:如果不事先調(diào)用showdown()方法,則此方法永遠(yuǎn)返回false.
    • boolean awaitTermination(long timeout, TimeUnit unit); 當(dāng)調(diào)用shotdown()方法后,調(diào)用此方法可以設(shè)置等待時(shí)間,等待執(zhí)行中的任務(wù)全部結(jié)束,全部結(jié)束返回true.如果超時(shí),或線程中斷導(dǎo)致未全部結(jié)束則返回false.
    • <T> Future<T> submit(Callable<T> task); 提交有返回值的Runnable任務(wù).
    • <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) 執(zhí)行傳入的任務(wù),當(dāng)所有任務(wù)執(zhí)行結(jié)束或超時(shí)后,返回持有任務(wù)狀態(tài)和結(jié)果的Future集合.注意:一個(gè)任務(wù)結(jié)束有兩種情況:1.正常執(zhí)行完成;2.拋出異常.
    • <T> T invokeAny(Collection<? extends Callable<T>> tasks); 執(zhí)行傳入的任務(wù),只要有任何一個(gè)任務(wù)成功執(zhí)行完成(不拋出異常),一旦有結(jié)果返回,或拋出異常,則其他任務(wù)取消.
  • ScheduledExecutorService
    ExecutorService的子接口,定義了延遲或周期性執(zhí)行Runnable任務(wù)的方法.

  • AbstractExecutorService
    ExecutorService的默認(rèn)抽象實(shí)現(xiàn)類(lèi),對(duì)ExecutorService進(jìn)行了簡(jiǎn)單實(shí)現(xiàn),開(kāi)發(fā)者可以參考并重寫(xiě)這些方法.

  • SerialExecutorService
    ExecutorService的子接口,標(biāo)記型接口,該類(lèi)型的線程池會(huì)以隊(duì)列(先進(jìn)先出)的順序執(zhí)行提交的任務(wù).

  • ThreadPoolExecutor
    AbstractExecutorService子接口的默認(rèn)實(shí)現(xiàn)類(lèi),可以使用這個(gè)類(lèi)自定義線程池使用.系統(tǒng)提供的幾種常用線程池,最終都是通過(guò)此類(lèi)來(lái)創(chuàng)建.

  • ScheduledThreadPoolExecutor
    ScheduledExecutorService子接口的實(shí)現(xiàn)類(lèi),繼承ThreadPoolExecutor,用于延遲或周期性執(zhí)行Runnable任務(wù).

3.如何創(chuàng)建線程池

Executor是Java中的一個(gè)接口,其默認(rèn)實(shí)現(xiàn)類(lèi)是ThreadPoolExecutor,構(gòu)造方法如下

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

ThreadPoolExecutor一共有4個(gè)重載的構(gòu)造方法,上述代碼中的后三位參數(shù)都是可選參數(shù).
通過(guò)構(gòu)造方法即可自定義線程池.然后通過(guò)execute()來(lái)執(zhí)行Runnable任務(wù).

涉及到的幾個(gè)參數(shù)解釋如下:

  • corePoolSize

線程池的核心線程數(shù),默認(rèn)情況下,核心線程會(huì)在線程池中一直存活,即使它們處于閑置狀態(tài).如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)置為true,那么閑置的[核心線程]在等待新任務(wù)到來(lái)時(shí)會(huì)有超時(shí)策略,這個(gè)時(shí)間間隔由keepAliveTime所指定,當(dāng)?shù)却龝r(shí)間超過(guò)keepAliveTime所指定的時(shí)長(zhǎng)后,核心線程就會(huì)被終止.

  • maximumPoolSize

線程池所能容納的最大線程數(shù),當(dāng)活動(dòng)線程數(shù)達(dá)到這個(gè)數(shù)值后,后續(xù)的新任務(wù)將會(huì)被阻塞.

  • keepAliveTime

非核心線程閑置時(shí)的超時(shí)時(shí)長(zhǎng),超過(guò)這個(gè)時(shí)長(zhǎng),非核心線程就會(huì)被回收.當(dāng)ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)置為true時(shí), keepAliveTime同樣會(huì)作用于核心線程.

這個(gè)[?;顣r(shí)間]設(shè)計(jì)的巧妙之處在于:當(dāng)線程處于閑置狀態(tài)時(shí),并不馬上銷(xiāo)毀,而是在指定時(shí)間段內(nèi)將其緩存在線程池中,以方便在限定的時(shí)間段內(nèi)如果再有任務(wù)來(lái)臨,能夠快速的重新啟用等待中的線程.處于閑置狀態(tài)的空閑線程并不會(huì)占用多少內(nèi)存,而且這樣就能顯著減少頻繁的創(chuàng)建,銷(xiāo)毀線程造成的內(nèi)存消耗及性能下降.

  • unit

用于指定keepAliveTime參數(shù)的時(shí)間單位,這是一個(gè)枚舉,常用的有TimeUnit.NANOSECONDS(毫秒),TimeUnit.SECONDS(秒),TimeUnit.MINUTES(分鐘)等

  • workQueue

線程池中等待被執(zhí)行的任務(wù)隊(duì)列,這個(gè)隊(duì)列僅持有通過(guò)execute方法提交的Runnable任務(wù).

  • threadFactory

線程工廠,ThreadFactory是個(gè)接口,它只有一個(gè)方法,·Thread newThread(Runnable r),executor創(chuàng)建新線程時(shí)調(diào)用

  • RejectedExecutionHandler 當(dāng)由于線程阻塞,任務(wù)隊(duì)列容量已滿(mǎn)等因素導(dǎo)致無(wú)法成功執(zhí)行任務(wù)時(shí),這個(gè)handler會(huì)調(diào)用rejectedExecution方法來(lái)通知調(diào)用者.

4.系統(tǒng)封裝的4種線程池

這里要使用到Executors類(lèi),它是Executor體系的靜態(tài)工廠類(lèi),類(lèi)中封裝了一些創(chuàng)建線程池的方法.
Executor與其子接口及其實(shí)現(xiàn)類(lèi)負(fù)責(zé)定義和規(guī)范線程池,而Executors負(fù)責(zé)創(chuàng)建線程池.

4.1 SingleThreadExecutor

SingleThreadExecutor內(nèi)部只有一個(gè)核心線程,它確保所有的任務(wù)都在同一個(gè)線程中按順序執(zhí)行.
它的意義在于統(tǒng)一所有的外界任務(wù)到一個(gè)線程中,使得在這些任務(wù)之間不需要處理線程同步的問(wèn)題.

內(nèi)部實(shí)現(xiàn)如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

代碼測(cè)試:

private void singleThreadExecutorTest() {
    //單一線程池
    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    //開(kāi)啟5次線程
    for (int i = 0; i < 5; i++) {
        singleThreadExecutor.execute(new MyThread());
    }
    //關(guān)閉線程池
    singleThreadExecutor.shutdown();
}

Log輸出:

System.out: pool-1-thread-1 執(zhí)行
System.out: pool-1-thread-1 執(zhí)行
System.out: pool-1-thread-1 執(zhí)行
System.out: pool-1-thread-1 執(zhí)行
System.out: pool-1-thread-1 執(zhí)行

4.2 FixedThreadPoolExecutor

FixedThreadPoolExecutor是一種線程數(shù)量固定的線程池,只有核心線程,沒(méi)有超時(shí)機(jī)制,任務(wù)隊(duì)列沒(méi)有大小限制.
當(dāng)所有的線程都處于活動(dòng)狀態(tài)時(shí),新任務(wù)都會(huì)處于等待狀態(tài),直到有線程空閑出來(lái).
當(dāng)線程處于空閑狀態(tài)時(shí),它們并不會(huì)被回收,除非線程池被關(guān)閉了.這意味著它能夠更加快速的響應(yīng)外界的請(qǐng)求.

//構(gòu)造方法
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

代碼測(cè)試:

/**自定義一個(gè)線程類(lèi),繼承Thread,打印Log*/
class MyThread extends Thread{

    public void run() {
        System.out.println(Thread.currentThread().getName()+" 執(zhí)行");
    }
}

private void fixedThreadPoolTest() {
    //容量為2的固定線程池
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);

    //創(chuàng)建5條線程
    for (int i = 0; i < 5; i++) {
        fixedThreadPool.execute(new MyThread());
    }
    //關(guān)閉線程池
    fixedThreadPool.shutdown();
}

Log輸出:

System.out: pool-1-thread-1 執(zhí)行
System.out: pool-1-thread-2 執(zhí)行
System.out: pool-1-thread-2 執(zhí)行
System.out: pool-1-thread-2 執(zhí)行
System.out: pool-1-thread-1 執(zhí)行

可以發(fā)現(xiàn),雖然new了5個(gè)Thread,但是系統(tǒng)只用兩條線程來(lái)執(zhí)行5個(gè)任務(wù).

4.3 CachedThreadPool

CachedThreadPool線程數(shù)量不定,只有非核心線程,并且其最大線程數(shù)為Integer.MAX_VALUE(相當(dāng)于無(wú)限大)
當(dāng)線程池中的線程都處于活動(dòng)狀態(tài)時(shí),線程池會(huì)創(chuàng)建新的線程來(lái)處理新任務(wù),否則就會(huì)利用空閑的線程來(lái)處理新任務(wù).

空閑線程都有超時(shí)時(shí)機(jī),這個(gè)超時(shí)時(shí)長(zhǎng)為60秒,限制超60秒就會(huì)被回收.

和FixedThreadPoolExecutor不同的是,CachedThreadPool的任務(wù)隊(duì)列其實(shí)相當(dāng)一個(gè)空集合,這將導(dǎo)致任何任務(wù)都會(huì)被立即執(zhí)行,因?yàn)榇藭r(shí)SynchronousQueue是無(wú)法插入任務(wù)的.
CachedThreadPool線程池比較適合執(zhí)行大量+耗時(shí)較少的任務(wù).當(dāng)整個(gè)線程池都處于閑置狀態(tài)時(shí),會(huì)因超時(shí)而被停止,此時(shí)沒(méi)有線程的線程池幾乎不占用任何系統(tǒng)資源.

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

代碼測(cè)試:

private void cachedThreadPoolTest() {
    //緩存線程池
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

    //創(chuàng)建5條線程
    for (int i = 0; i < 5; i++) {
        cachedThreadPool.execute(new MyThread());
    }
    //關(guān)閉線程池
    cachedThreadPool.shutdown();
}

Log輸出:

System.out: pool-1-thread-1執(zhí)行
System.out: pool-1-thread-1執(zhí)行
System.out: pool-1-thread-2執(zhí)行
System.out: pool-1-thread-3執(zhí)行
System.out: pool-1-thread-4執(zhí)行

4.4 ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor核心線程數(shù)固定,非核心線程數(shù)沒(méi)有限制,并且非核心線程閑置時(shí)會(huì)被回收
主要用于執(zhí)行定時(shí)任務(wù) 和 具有固定周期的重復(fù)任務(wù).

//Executors靜態(tài)方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//繼續(xù)追查ScheduledThreadPoolExecutor源碼,會(huì)發(fā)現(xiàn)最終還是通過(guò)ThreadPoolExecutor的構(gòu)造來(lái)創(chuàng)建
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

常用方法:

  • schedule(Runnable command,long delay, TimeUnit unit)XXX時(shí)間之后,執(zhí)行指定的任務(wù)
  • scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
    XXX時(shí)間后,執(zhí)行指定任務(wù),每隔XXX時(shí)間執(zhí)行一次

代碼測(cè)試:

private void scheduledThreadPoolTest() {
    //定時(shí)任務(wù)線程池
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);

    //3秒后打印一次Log
    scheduledThreadPool.schedule(new Runnable() {
        @Override
        public void run() {
            System.out.println("~~~~~~~~~3秒之后露個(gè)臉~~~~~~~~~");
        }
    },3000,TimeUnit.MILLISECONDS);

    //0秒初始化延遲,每秒打印Log
    scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            System.out.println("每隔一秒打印一次");
        }
    },0,1000, TimeUnit.MILLISECONDS);
}

Log輸出:

System.out: 每隔一秒打印一次
System.out: 每隔一秒打印一次
System.out: 每隔一秒打印一次
System.out: ~~~~~~~~~3秒之后露個(gè)臉~~~~~~~~~
System.out: 每隔一秒打印一次
System.out: 每隔一秒打印一次
System.out: 每隔一秒打印一次

5.總結(jié)

可以發(fā)現(xiàn),系統(tǒng)提供的4種線程池,最終都是通過(guò)配置ThreadPoolExecutor的不同參數(shù),來(lái)巧妙的達(dá)到不同的線程管理效果.

以上只是關(guān)于線程池的一些基礎(chǔ)認(rèn)知,下一篇進(jìn)行 線程池運(yùn)行原理分析.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容