【多線程】線程池理論知識(shí)

上篇文章講了下線程的創(chuàng)建及一些常用的方法镰惦,但是在使用的時(shí)候,大多數(shù)是采用了線程池來(lái)管理線程的創(chuàng)建犬绒,運(yùn)行旺入,銷毀等過(guò)程。本篇將著重講線程池的基礎(chǔ)內(nèi)容凯力,包括通過(guò)線程池創(chuàng)建線程茵瘾,線程池的基本信息等。

創(chuàng)建線程

前期準(zhǔn)備

本小節(jié)所有代碼都是在CreateThreadByPool 類上咐鹤,該類還有一個(gè)內(nèi)部類MyThread 實(shí)現(xiàn)了Runnable 接口拗秘。

首先先把基本的代碼給寫(xiě)出來(lái)

public class CreateThreadByPool {
    public static void main(String[] args) {

    }
}

class MyThread implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " processing");
        process();
        System.out.println(Thread.currentThread().getName() + " end");
    }

    private void process() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return String.format("MyThread{%s}", Thread.currentThread().getName());
    }
}

先來(lái)大概回顧一下,當(dāng)我們想創(chuàng)建10個(gè)線程的時(shí)候的代碼普通方式是怎樣的

private static void createThreadByNormalWay() {
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.start();
    }
}

能看到的代碼中祈惶,是使用了start() 自己直接開(kāi)啟了線程雕旨,但是如果用線程池方式來(lái)呢

通過(guò)Executors

第一種創(chuàng)建線程池的方法是通過(guò)Executors 類的靜態(tài)方法來(lái)構(gòu)建,通過(guò)這種方式總共可以創(chuàng)建4種線程池捧请。

image

并且可以發(fā)現(xiàn)返回是ExecutorService 凡涩,所以還要接受返回值,最后通過(guò)execute 來(lái)啟動(dòng)線程

private static void createThreadByPool() {
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        executorService.execute(myThread);
    }
}

先不管底層是如何實(shí)現(xiàn)的疹蛉,至少代碼上是把線程交給了線程池來(lái)執(zhí)行活箕,這樣能夠保證線程能夠統(tǒng)一管理。

簡(jiǎn)單的比喻就是前者是要你自己去找班長(zhǎng)簽到可款,后者是班長(zhǎng)統(tǒng)一管理這整個(gè)班的簽到育韩。在main函數(shù)中調(diào)用看看普通方法和通過(guò)線程池創(chuàng)建的線程有什么區(qū)別

threadPool1

可以很明顯的看到有以下幾點(diǎn)區(qū)別

  • 線程的名字都不一樣
  • 并且普通方式是創(chuàng)建了10個(gè)線程,而后者只是創(chuàng)建了5個(gè)線程(是由我們自己設(shè)定的
  • 前者基本上是10個(gè)線程都是同時(shí)處理筑舅,后者是最多只能處理5個(gè)線程座慰,需要等線程執(zhí)行完有空閑才能處理其它線程。

通過(guò)ThreadPoolExecutor

除了使用Executors.newFixedThreadPool() 創(chuàng)建線程池翠拣,還可以通過(guò)new ThreadPoolExecutor() 版仔,這里可能有的小伙伴會(huì)迷糊了,怎么上面放回的類是ExecutorService ,現(xiàn)在返回的又是ThreadPoolExecutor 蛮粮,其實(shí)兩者是同一個(gè)東西益缎。

image

可以看到ThreadExecutorPool 是繼承了 AbstractExecutorService ,而后者是實(shí)現(xiàn)了ExecutorService 然想。通過(guò)該方法創(chuàng)建的線程池的代碼如下

可以先這樣運(yùn)行體驗(yàn)下莺奔,至于說(shuō)構(gòu)造函數(shù)里面不同參數(shù)的含義,在后面的篇幅中會(huì)說(shuō)到变泄,到時(shí)候再返回來(lái)看即可令哟。

private static void createThreadByThreadPoolExecutor() {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
    for (int i = 0; i < 10; i++) {
        MyThread myThread = new MyThread();
        executor.execute(myThread);
    }
}

看下運(yùn)行結(jié)果

image

輸出結(jié)果沒(méi)啥好講的,但是如果細(xì)心的小伙伴在上一個(gè)gif就會(huì)發(fā)現(xiàn)妨蛹,通過(guò)線程池來(lái)啟動(dòng)線程的方式屏富,程序并沒(méi)有退出,會(huì)一直運(yùn)行蛙卤。這是因?yàn)槲覀儧](méi)有shutdown 線程池狠半。

兩者區(qū)別

回過(guò)頭來(lái)看看Executors.靜態(tài)方法 這種方法來(lái)創(chuàng)建線程池的源碼

image

可以看到其實(shí)更深一層還是使用了new ThreadPoolExecutor() ,只不過(guò)我們自己能定制的構(gòu)造函數(shù)的參數(shù)變得極其少颤难,這時(shí)候肯定有小伙伴疑問(wèn)了神年,那為什么不直接都用new ThreadPoolExecutor() 呢?

《阿里java開(kāi)發(fā)手冊(cè)》 嵩山版明確規(guī)定了兩點(diǎn)行嗤,一是線程資源必須通過(guò)線程池提供已日,不允許自行顯式創(chuàng)建線程;二是線程池不允許使用Executors去創(chuàng)建昂验,而是通過(guò)ThreadPoolExecutor的方式去創(chuàng)建捂敌。

image

著重看第二點(diǎn)強(qiáng)制通過(guò)ThreadPoolExecutor的方式來(lái)創(chuàng)建線程,原因在下面也有既琴,來(lái)看看FixedThreadPool和SingleThreadPool的源碼

image
image-20210629161632466

其它的不管,可以看到兩者調(diào)用構(gòu)造函數(shù)中的隊(duì)列都是LinkedBlockingQueue 泡嘴,這個(gè)隊(duì)列是無(wú)邊界的甫恩,所以有了允許請(qǐng)求長(zhǎng)度為Integer.MAX_VALUE ,會(huì)堆積大量的請(qǐng)求 酌予,從而導(dǎo)致OOM磺箕。

再來(lái)看看CachedThreadPool的源碼

image

注意這里構(gòu)造函數(shù)的第二個(gè)參數(shù)是線程池最大線程數(shù),它設(shè)置成了Integer.MAX_VALUE 抛虫,這就可能會(huì)創(chuàng)建大量的線程松靡,從而導(dǎo)致OOM。

線程池信息

ThreadPoolExecutor

上面也可以看到建椰,創(chuàng)建線程池最重要也是最應(yīng)該使用的方法的是new ThreadPoolExecutor() 雕欺,接下來(lái)把重點(diǎn)放在ThreadPoolExecutor這個(gè)類上面

image

這個(gè)是類中的所有的屬性,接下來(lái)再看看構(gòu)造函數(shù)

image

有4種,但是歸根結(jié)底只有以下這一種構(gòu)造函數(shù)屠列,講下這些參數(shù)的意義啦逆,然后大家就可以回頭看下上一小節(jié)的例子。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    //省略實(shí)現(xiàn)
}
  • corePoolSize :核心線程數(shù)笛洛,大白話就是能夠工作的線程數(shù)量

  • maximumPoolSize :最大線程數(shù)夏志,就是這個(gè)線程池能容納線程的數(shù)量

  • keepAliveTime :存活時(shí)間,當(dāng)線程池中的線程數(shù)量大于核心線程數(shù)的時(shí)候苛让,如果時(shí)候沒(méi)有任務(wù)提交沟蔑,核心線程池外的線程不會(huì)立即被銷毀,而是會(huì)等待狱杰,直到等待的時(shí)間超過(guò)了這個(gè)字段才會(huì)被回收銷毀

  • unit :存活時(shí)間的單位

  • workQueue :工作隊(duì)列瘦材,就是在線程開(kāi)始被調(diào)用前,就是存在這個(gè)隊(duì)列中

  • threadFactory :線程工廠浦旱,執(zhí)行程序創(chuàng)建新線程時(shí)使用的工廠

  • handler :拒絕策略宇色,當(dāng)達(dá)到線程邊界和隊(duì)列容量而采取的拒絕策略

對(duì)于這個(gè)拒絕策略,簡(jiǎn)單說(shuō)下颁湖,有四種實(shí)現(xiàn)宣蠕。

實(shí)現(xiàn)RejectedExecutionHandler 接口就能實(shí)現(xiàn)自己的拒絕策略

image

監(jiān)控線程

下面就來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)自己的拒絕策略,并且來(lái)看下上述類中屬性的信息

首先需要一個(gè)監(jiān)控線程類

class MonitorThread implements Runnable {
    
    //注入一個(gè)線程池
    private ThreadPoolExecutor executor;

    public MonitorThread(ThreadPoolExecutor executor) {
        this.executor = executor;
    }

    private boolean monitor = true;

    public void stopMonitor() {
        monitor = false;
    }

    @Override
    public void run() {
        //監(jiān)控一直運(yùn)行甥捺,每3s輸出一次狀態(tài)
        while (monitor) {
            //主要邏輯是監(jiān)控線程池的狀態(tài)
            System.out.println(
                    String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s",
                            this.executor.getPoolSize(),
                            this.executor.getCorePoolSize(),
                            this.executor.getActiveCount(),
                            this.executor.getCompletedTaskCount(),
                            this.executor.getTaskCount(),
                            this.executor.isShutdown(),
                            this.executor.isTerminated(),
                            this.executor.getRejectedExecutionHandler()));

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

同時(shí)實(shí)現(xiàn)自定義的拒絕策略

其實(shí)這還是沒(méi)有對(duì)r處理抢蚀,拒絕了就拒絕了,只是打印出來(lái)镰禾,但是并沒(méi)有實(shí)質(zhì)性地處理

class MyRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("task is rejected");
    }
}

接下來(lái)就是public類TheradPoolInfo 皿曲,注意工作線程采用的是上一小節(jié)的MyThread

public class ThreadPoolInfo {
    public static void main(String[] args) throws InterruptedException {
        //新建了一個(gè)線程池,核心線程數(shù)是3吴侦,最大線程數(shù)是5屋休,30s
        //隊(duì)列是ArrayBlockingQueue,并且大小邊界是3备韧,拒絕策略自定義輸出一句話
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new MyRejectedExecutionHandler());
        
        //開(kāi)啟監(jiān)控線程
        MonitorThread monitorThread = new MonitorThread(executor);
        new Thread(monitorThread).start();
        
        //開(kāi)啟工作線程
        for (int i = 0; i < 10; i++) {
            executor.execute(new MyThread());
        }
        
        //關(guān)閉線程池和監(jiān)控線程
        Thread.sleep(12000);
        executor.shutdown();
        Thread.sleep(3000);
        monitorThread.stopMonitor();
    }
}

預(yù)期結(jié)果: 通過(guò)構(gòu)造函數(shù)可以知道劫樟,預(yù)期是有3個(gè)核心線程執(zhí)行任務(wù),會(huì)拒絕2個(gè)線程织堂,完成8個(gè)任務(wù)(最大線程數(shù)是5叠艳,隊(duì)列長(zhǎng)度是3,具體會(huì)在下一篇文章中講)易阳。

image

可以看到結(jié)果和預(yù)期的一樣

image

創(chuàng)作不易附较,如果對(duì)你有幫助,歡迎點(diǎn)贊潦俺,收藏和分享啦拒课!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末徐勃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捕发,更是在濱河造成了極大的恐慌疏旨,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扎酷,死亡現(xiàn)場(chǎng)離奇詭異檐涝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)法挨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)谁榜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人凡纳,你說(shuō)我怎么就攤上這事窃植。” “怎么了荐糜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵巷怜,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我暴氏,道長(zhǎng)延塑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任答渔,我火速辦了婚禮关带,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沼撕。我一直安慰自己宋雏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布务豺。 她就那樣靜靜地躺著磨总,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笼沥。 梳的紋絲不亂的頭發(fā)上舍败,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音敬拓,去河邊找鬼。 笑死裙戏,一個(gè)胖子當(dāng)著我的面吹牛乘凸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播累榜,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼营勤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼灵嫌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起葛作,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤寿羞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后赂蠢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绪穆,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年虱岂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玖院。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡第岖,死狀恐怖难菌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蔑滓,我是刑警寧澤郊酒,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站键袱,受9級(jí)特大地震影響燎窘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杠纵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一荠耽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧比藻,春花似錦铝量、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至务蝠,卻和暖如春拍谐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背馏段。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工轩拨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人院喜。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓亡蓉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親喷舀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砍濒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容