常見的線程池有哪些

這篇文章主要介紹“常見的線程池有哪些”,在日常操作中,相信很多人在常見的線程池有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”常見的線程池有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

成都創(chuàng)新互聯(lián)專注于原州網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供原州營銷型網(wǎng)站建設(shè),原州網(wǎng)站制作、原州網(wǎng)頁設(shè)計、原州網(wǎng)站官網(wǎng)定制、微信平臺小程序開發(fā)服務(wù),打造原州網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供原州網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

一:幾種常見線程池

1.1 FixedThreadPool

FixedThreadPool線程池,它的核心線程數(shù)和最大線程數(shù)是一樣的,所以可以把它看作是固定線程數(shù)的線程池。

特點是:線程池中的線程數(shù)除了初始階段需要從 0 開始增加外,之后的線程數(shù)量就是固定的,就算任務(wù)數(shù)超過線程數(shù),線程池也不會再創(chuàng)建更多的線程來處理任務(wù),而是會把超出線程處理能力的任務(wù)放到任務(wù)隊列中進(jìn)行等待。而且就算任務(wù)隊列滿了,到了本該繼續(xù)增加線程數(shù)的時候,由于它的最大線程數(shù)和核心線程數(shù)是一樣的,所以也無法再增加新的線程了。

常見的線程池有哪些

如圖所示,線程池有 t0~t9,10 個線程,它們會不停地執(zhí)行任務(wù),如果某個線程任務(wù)執(zhí)行完了,就會從任務(wù)隊列中獲取新的任務(wù)繼續(xù)執(zhí)行,期間線程數(shù)量不會增加也不會減少,始終保持在 10 個。

1.2 CachedThreadPool

CachedThreadPool,可以稱作可緩存線程池,它的特點在于線程數(shù)是幾乎可以無限增加的(實際最大可以達(dá)到 Integer.MAX_VALUE,為 2^31-1),而當(dāng)線程閑置時還可以對線程進(jìn)行回收。

CachedThreadPool 線程池它也有一個用于存儲提交任務(wù)的隊列,但這個隊列是 SynchronousQueue,隊列的容量為0,實際不存儲任何任務(wù),它只負(fù)責(zé)對任務(wù)進(jìn)行中轉(zhuǎn)和傳遞,所以效率比較高

當(dāng)提交一個任務(wù)后,線程池會判斷已創(chuàng)建的線程中是否有空閑線程,如果有空閑線程則將任務(wù)直接指派給空閑線程,如果沒有空閑線程,則新建線程去執(zhí)行任務(wù),這樣就做到了動態(tài)地新增線程。如下方代碼所示。

/**
 * @date 2020/11/15 23:17
 *
 * @discription 緩存線程池
 */
public class CachedThreadPool {
    
    public static void main(String[] args) {

        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) {
            service.execute(new ThreadPoolDemo.Task());
        }
    }
    static class Task implements Runnable{
        @Override
        public void run() {
            System.out.println("Thread Name: " + Thread.currentThread().getName());
        }
    }
}

因為執(zhí)行任務(wù)簡單,所以在 for 循環(huán)提交任務(wù)結(jié)束前,就可能導(dǎo)致先執(zhí)行的任務(wù)被執(zhí)行完了,線程空閑出來執(zhí)行后面的任務(wù),從而不會出現(xiàn)線程名稱為pool-1-thread-1000的線程。

假設(shè)這些任務(wù)處理的時間非常長,因為 for 循環(huán)提交任務(wù)的操作是非??斓?,但執(zhí)行任務(wù)卻比較耗時,就可能導(dǎo)致 1000 個任務(wù)都提交完了但第一個任務(wù)還沒有被執(zhí)行完,所以此時 CachedThreadPool 就可以動態(tài)的伸縮線程數(shù)量,隨著任務(wù)的提交,不停地創(chuàng)建 1000 個線程來執(zhí)行任務(wù),而當(dāng)任務(wù)執(zhí)行完之后,假設(shè)沒有新的任務(wù)了,那么大量的閑置線程又會造成內(nèi)存資源的浪費,這時線程池就會檢測線程在 60 秒內(nèi)有沒有可執(zhí)行任務(wù),如果沒有就會被銷毀,最終線程數(shù)量會減為 0。

1.3 ScheduledThreadPool

線程池 ScheduledThreadPool,它支持定時或周期性執(zhí)行任務(wù)。

比如每隔 10 秒鐘執(zhí)行一次任務(wù),而實現(xiàn)這種功能的方法主要有 3 種,如代碼所示:

/**
 * @date 2020/11/15 23:34
 *
 * @discription 支持定時或周期性執(zhí)行任務(wù)的線程池
 */
public class ScheduledThreadPool {

    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

        /**
         * schedule —— 比較簡單表示延遲指定時間后執(zhí)行一次任務(wù),
         * 如果代碼中設(shè)置參數(shù)為 10 秒,也就是 10 秒后執(zhí)行一次任務(wù)后就結(jié)束。
         */
        service.schedule(new ThreadPoolDemo.Task(),10, TimeUnit.SECONDS);

        /**
         * scheduleAtFixedRate —— 表示以固定的頻率執(zhí)行任務(wù),
         * 它的第二個參數(shù) initialDelay 表示第一次延時時間,
         * 第三個參數(shù) period 表示周期也就是第一次延時后每次延時多長時間執(zhí)行一次任務(wù)。
         */
        service.scheduleAtFixedRate(new ThreadPoolDemo.Task(), 10, 10, TimeUnit.SECONDS);

        /**
         * scheduleWithFixedDelay —— 與第二種方法類似,也是周期執(zhí)行任務(wù),區(qū)別在于對周期的定義,
         * 之前的 scheduleAtFixedRate 是以任務(wù)開始的時間為時間起點,
		 開始計時時間到就開始執(zhí)行第二次任務(wù),而不管任務(wù)需要花多久執(zhí)行;
         * 而 scheduleWithFixedDelay 方法以任務(wù)結(jié)束的時間為下一次循環(huán)的時間起點開始計時。
         */
        service.scheduleWithFixedDelay(new ThreadPoolDemo.Task(), 10, 10, TimeUnit.SECONDS);
    }
}

比如:假設(shè)某個同學(xué)正在熬夜寫代碼,需要喝咖啡來提神,假設(shè)每次喝咖啡都需要花10分鐘的時間,如果此時采用第2種方法 scheduleAtFixedRate,時間間隔設(shè)置為 1 小時,那么他將會在每個整點喝一杯咖啡,以下是時間表:

  • 00:00: 開始喝咖啡

  • 00:10: 喝完了

  • 01:00: 開始喝咖啡

  • 01:10: 喝完了

  • 02:00: 開始喝咖啡

  • 02:10: 喝完了

采用第3種方法 scheduleWithFixedDelay,時間間隔同樣設(shè)置為 1 小時,那么由于每次喝咖啡需要10分鐘,而 scheduleWithFixedDelay 是以任務(wù)完成的時間為時間起點開始計時的,所以第2次喝咖啡的時間將會在1:10,而不是1:00整,以下是時間表:

  • 00:00: 開始喝咖啡

  • 00:10: 喝完了

  • 01:10: 開始喝咖啡

  • 01:20: 喝完了

  • 02:20: 開始喝咖啡

  • 02:30: 喝完了

1.4 SingleThreadExecutor

SingleThreadExecutor,它會使用唯一的線程去執(zhí)行任務(wù),原理和 FixedThreadPool 是一樣的,只不過這里線程只有一個,如果線程在執(zhí)行任務(wù)的過程中發(fā)生異常,線程池也會重新創(chuàng)建一個線程來執(zhí)行后續(xù)的任務(wù)。

這種線程池由于只有一個線程,所以非常適合用于所有任務(wù)都需要按被提交的順序依次執(zhí)行的場景,而前幾種線程池不一定能夠保障任務(wù)的執(zhí)行順序等于被提交的順序,因為它們是多線程并行執(zhí)行的。

1.5 SingleThreadScheduledExecutor

SingleThreadScheduledExecutor,它實際和第三種 ScheduledThreadPool 線程池非常相似,它只是 ScheduledThreadPool 的一個特例,內(nèi)部只有一個線程。

注意:它只是將 ScheduledThreadPool 的核心線程數(shù)設(shè)置為了 1,即new ScheduledThreadPoolExecutor(1),并非最大線程為一的意思。

1.6 總結(jié)

常見的線程池有哪些

二:JDK1.7 新增新增線程池 ForkJoinPool

ForkJoinPool,這個線程池是在 JDK 7 加入的,主要用法和之前的線程池是相同的,也是把任務(wù)交給線程池去執(zhí)行,線程池中也有任務(wù)隊列來存放任務(wù)。ForkJoinPool 非常適合用于遞歸的場景,例如樹的遍歷、最優(yōu)路徑搜索等場景。

ForkJoinPool 線程池和之前的線程池有兩點非常大的不同之處。

第一點它非常適合執(zhí)行可以產(chǎn)生子任務(wù)的任務(wù)

比如:我們有一個 Task,這個 Task 可以產(chǎn)生三個子任務(wù),三個子任務(wù)并行執(zhí)行完畢后將結(jié)果匯總給 Result,比如說主任務(wù)需要執(zhí)行非常繁重的計算任務(wù),我們就可以把計算拆分成三個部分,這三個部分是互不影響相互獨立的,這樣就可以利用 CPU 的多核優(yōu)勢,并行計算,然后將結(jié)果進(jìn)行匯總。這里面主要涉及兩個步驟,第一步是拆分也就是 Fork,第二步是匯總也就是 Join,這也是ForkJoinPool 線程池名字的由來。

ForkJoinPool 線程池有多種方法可以實現(xiàn)任務(wù)的分裂和匯總,其中一種用法計算斐波那契數(shù)列如下方代碼所示。

/**
 * @date 2020/11/16 0:06
 *
 * @discription ForkJoinPool計算斐波那契數(shù)列
 *              打印出斐波那契數(shù)列的第 0 到 9 項的值:
 */
public class Fibonacci extends RecursiveTask<Integer> {

    int n;

    public Fibonacci(int n) {
        this.n = n;
    }

    @Override
    protected Integer compute() {
        if(n <= 1){
            return n;
        }
        Fibonacci f1 = new Fibonacci(n-1);
        f1.fork();
        Fibonacci f2 = new Fibonacci(n-2);
        f2.fork();

        return f1.join() + f2.join();
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        for (int i = 0; i < 10; i++) {
            ForkJoinTask task = forkJoinPool.submit(new Fibonacci(i));
            System.out.println(task.get());
        }
    }
}

它首先繼承了 RecursiveTask,RecursiveTask 類是對ForkJoinTask 的一個簡單的包裝,這時我們重寫 compute() 方法,當(dāng) n<=1 時直接返回,當(dāng) n>1 就創(chuàng)建遞歸任務(wù),也就是 f1 和 f2,然后我們用 fork() 方法分裂任務(wù)并分別執(zhí)行,最后在 return 的時候,使用 join() 方法把結(jié)果匯總,這樣就實現(xiàn)了任務(wù)的分裂和匯總。

第二點不同之處在于內(nèi)部結(jié)構(gòu)

之前的線程池所有的線程共用一個隊列,但 ForkJoinPool 線程池中每個線程都有自己獨立的任務(wù)隊列,如圖所示。

常見的線程池有哪些

可以看到 ForkJoinPool 線程池和其他線程池很多地方都是一樣的,但重點區(qū)別在于除了有一個共用的任務(wù)隊列之外,它每個線程都有一個自己的雙端隊列來存儲分裂出來的子任務(wù)。

  • 當(dāng)線程中的任務(wù)被 Fork 分裂了,分裂出來的子任務(wù)放入線程自己的 deque 里,而不是放入公共的任務(wù)隊列中。對于當(dāng)前線程來說以直接在自己的任務(wù)隊列中獲取,而不必去公共隊列中爭搶,也不會發(fā)生阻塞(除了 steal 情況外),減少了線程間的競爭和切換,是非常高效的。

  • 若此時線程有多個,而線程 t1 的任務(wù)特別繁重,分裂了數(shù)十個子任務(wù),但是 t0 此時卻無事可做,它自己的 deque 隊列為空,這時為了提高效率,t0 就會想辦法幫助 t1 執(zhí)行任務(wù),這就是“work-stealing”的含義。雙端隊列 deque 中,線程 t1 獲取任務(wù)的邏輯是后進(jìn)先出,而線程 t0 在“steal”偷線程 t1 的 deque 中的任務(wù)的邏輯是先進(jìn)先出,使用 “work-stealing” 算法和雙端隊列很好地平衡了各線程的負(fù)載

到此,關(guān)于“常見的線程池有哪些”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

文章名稱:常見的線程池有哪些
標(biāo)題來源:http://bm7419.com/article22/gippjc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、Google營銷型網(wǎng)站建設(shè)、服務(wù)器托管做網(wǎng)站、靜態(tài)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護(hù)公司