4. 線程池使用

線程池: 如果線程的數(shù)量很多,并且每個(gè)線程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束,這樣頻繁創(chuàng)建線程會(huì)大大增加系統(tǒng)的開銷,因?yàn)閯?chuàng)建和消毀線程都需要資源和時(shí)間的

線程池有4種

1) newCachedThreadPool

新建一個(gè)可緩存線程,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程

2) newFixedThreadPool

創(chuàng)建一個(gè)固定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待

3) newScheduleThreadTool

創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行

4) newSingleThreadExecutor

創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO,LIFO)優(yōu)先級(jí)執(zhí)行

簡(jiǎn)單實(shí)例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TreadPoolDemo {
    public static void main(String[] args) {                                            //執(zhí)行速度
        ExecutorService executorService1 = Executors.newCachedThreadPool();             //快
        ExecutorService executorService2 = Executors.newFixedThreadPool(10);  //中等
        ExecutorService executorService3 = Executors.newSingleThreadExecutor();         //慢

        for (int i = 0; i < 100; i++) {
            executorService1.execute(new MyTask(i));
//            executorService2.execute(new MyTask(i));
//            executorService3.execute(new MyTask(i));

        }
    }
}
class MyTask implements Runnable{
    int i = 0;
    public MyTask(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"一執(zhí)行第:"+i+"任務(wù)");
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

分析:

  1. newCachedThreadPool啟動(dòng)源碼:

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

    核心線程數(shù): 0
    最大線程數(shù)為: Integer.MAX_VALUE
    線程空閑回收時(shí)間為: 60秒
    工作隊(duì)列為: SynchronousQueue (這個(gè)隊(duì)列只接受一個(gè)任務(wù),下面會(huì)講解到)

    newCachedThreadPool個(gè)人總結(jié) :
    所以該線程池執(zhí)行的邏輯是,有多少個(gè)任務(wù)我就開啟多少個(gè)線程去運(yùn)行該任務(wù)(缺點(diǎn):造成性能的浪費(fèi),容易導(dǎo)致服務(wù)器性能崩潰.優(yōu)點(diǎn)是:執(zhí)行速度快)

  2. newFixedThreadPool啟動(dòng)源碼

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    

    核心線程數(shù): 10 (傳入的線程數(shù)代碼上是10)
    最大線程數(shù)為: 10 (傳入的線程數(shù)代碼上是10)
    線程空閑回收時(shí)間為: 0
    工作隊(duì)列為: LinkedBlockingQueue (這個(gè)隊(duì)列能存儲(chǔ)多個(gè)任務(wù),下面會(huì)講解到)

    newFixedThreadPool個(gè)人總結(jié) :
    線程可復(fù)用,執(zhí)行速度合理,不存在非核心線程(優(yōu)先: 線程復(fù)用性強(qiáng) 缺點(diǎn):初始化后不能修改線程數(shù)目,遇到高并發(fā)的情況也只能使用初始設(shè)定的線程去執(zhí)行任務(wù),導(dǎo)致用戶體驗(yàn)較差)

  3. newSingleThreadExecutor啟動(dòng)源碼

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

    核心線程數(shù): 1
    最大線程數(shù)為: 1
    線程空閑回收時(shí)間為: 0
    工作隊(duì)列為: LinkedBlockingQueue (這個(gè)隊(duì)列能存儲(chǔ)多個(gè)任務(wù),下面會(huì)講解到)

    newSingleThreadExecutor個(gè)人總結(jié) :
    只有一個(gè)線程處理任務(wù)隊(duì)列(缺點(diǎn): 用戶體檢極差,處理能力極慢)

以上就是直接new一個(gè)線程池出來運(yùn)行,但是阿里開發(fā)手冊(cè)禁止使用該方法來啟動(dòng)一個(gè)線程池.而推薦使用ThreadPoolExecutor來創(chuàng)建

原因:

  1. 比如執(zhí)行newFixedThreadPool時(shí)會(huì)創(chuàng)建一個(gè)Integer.MAX_VALUE的線程隊(duì)列來存儲(chǔ)線程任務(wù),所有當(dāng)線程任務(wù)過多是(占用資源過多),會(huì)導(dǎo)致oom的問題
  2. 不能很好的配置核心線程數(shù)和最大線程數(shù),導(dǎo)致資源使用不合理,定義參數(shù)不合理

ThreadPoolExecutor講解

先看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;
}

構(gòu)造器提供了的參數(shù)我們來分析一下

  • int corePoolSize

    線程池中核心線程數(shù)的最大值

  • int maximumPoolSize

    線程池中能擁有最多線程數(shù)

  • long keepAliveTime

    表示空閑線程的存活時(shí)間(線程回收時(shí)間)

  • TimeUnit unit

    表示keepAliveTime的單位

  • BlockingQueue<Runnable> workQueue

    用于緩存任務(wù)阻塞隊(duì)列

    workQueue任務(wù)隊(duì)列:
    對(duì)于不用的場(chǎng)景我們可能會(huì)采取不同的排隊(duì)策略,排隊(duì)的策略需要實(shí)現(xiàn)一個(gè)BlockQueue接口的任務(wù)等待隊(duì)列一下任務(wù)隊(duì)列分為兩類

    1. 有限隊(duì)列
      1. SynchronousQueue: (一個(gè)不存元素的阻塞隊(duì)列)
        每個(gè)插入操作必須等到另一個(gè)線程調(diào)用移除操作否則插入操作一值處于阻塞狀態(tài),吞吐量通常高于LinkedBlockQueue
        2.ArrayBlockQueue: (有界阻塞隊(duì)列)
        一個(gè)由數(shù)組支持的有界隊(duì)列,此隊(duì)列按FIFO(先進(jìn)先出)原則對(duì)元素進(jìn)行排序,隊(duì)列獲取從頭部開始獲得元素,從尾部出入到隊(duì)列.固定大小的數(shù)據(jù),一但創(chuàng)建了緩存區(qū),就不能再增加容量了. 向已滿的隊(duì)列里放入元素會(huì)導(dǎo)致操作受阻塞,向空的隊(duì)列中提取元素也會(huì)導(dǎo)致阻塞
    2. 無限隊(duì)列
      1. LinkedBlockingQueue: (鏈表結(jié)構(gòu)的阻塞隊(duì)列)原理和ArrayBlockQueue隊(duì)列一樣,但是你可以指定容量,也可以不指定容量,不指定容量就是Integer.MAX_VALUE
      2. PriorityBlockIngQueue(一個(gè)具有優(yōu)先級(jí)的無限阻塞隊(duì)列)存放在PriorityBlockingQueue中的元素必須實(shí)現(xiàn)Comparable接口,這樣才能通過實(shí)現(xiàn)compareTo()方法進(jìn)行排序.優(yōu)先級(jí)最高的元素將始終排序到隊(duì)列的頭部,不會(huì)保證優(yōu)先級(jí)一樣的元素排序.
      3. LinkedBlockDeque和LinkedBlockingQueue一樣是基于鏈表的隊(duì)列,但是跟LinkedBlockingQueue不同的是,他兩頭都可以插入和取出元素
      4. LinkedTransferQueue 也是一個(gè)無限隊(duì)列伍宦,它除了具有一般隊(duì)列的操作特性外(先進(jìn)先出)勺届,還具有一個(gè)阻塞特性:LinkedTransferQueue可以由一對(duì)生產(chǎn)者/消費(fèi)者線程進(jìn)行操作些己,當(dāng)消費(fèi)者將一個(gè)新的元素插入隊(duì)列后,消費(fèi)者線程將會(huì)一直等待手素,直到某一個(gè)消費(fèi)者線程將這個(gè)元素取走,反之亦然胃榕。
  • ThreadFactory threadFactory

    指定創(chuàng)建線程的工廠

  • RejectedExecutionHandler handler

    表示當(dāng)workQueue隊(duì)列滿了,且池中的線程數(shù)到達(dá)maximumPoolSize時(shí),線程池采用的拒絕策略

策略 備注
ThreadPoolExecutor.AbortPolicy() 拋出RejectedExecutionException異常
ThreadPoolExecutor.CallerRunsPolicy() 由向線程池提交任務(wù)的線程來執(zhí)行該任務(wù)
ThreadPoolExecutor.DiscardPolicy() 拋棄當(dāng)前的任務(wù)
ThreadPoolExecutor.DiscardOldestPolicy() 拋棄最舊的任務(wù)(最先提交而沒有得到執(zhí)行的任務(wù))

阿里推薦我們使用ThreadPoolExecutor來創(chuàng)建線程池,原因是這樣創(chuàng)建線程比較靈活,可以根據(jù)自己的業(yè)務(wù)去定制線程池

ThreadPoolExecutor源碼這里就先不講解放到下篇文章講解,這里說說ThreadPoolExecutor的執(zhí)行過程,如何觸發(fā)maximumPoolSize的執(zhí)行線程啟動(dòng)
上例子:

import java.util.concurrent.*;

public class TreadPoolDemo {
    public static void main(String[] args) { 
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        // 3個(gè)核心線程
        3, 
        // 總共12個(gè)最多線程
        12,
        // 10秒空閑時(shí)間就回收
        10, 
        TimeUnit.SECONDS,
        //工作隊(duì)列大小
        new ArrayBlockingQueue<>(50),
        //策略為: 超出后丟棄
        new ThreadPoolExecutor.DiscardPolicy());
int i = 0;
        for (;;) {
            i ++;
            threadPoolExecutor.execute(new MyTask(i));

        }
    }
}
class MyTask implements Runnable{
    int i = 0;
    public MyTask(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"---執(zhí)行第:"+i+"任務(wù)");
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

以上的執(zhí)行結(jié)果我拷貝一定出來分析:

pool-4-thread-1一執(zhí)行第:1任務(wù)
pool-4-thread-2一執(zhí)行第:2任務(wù)
pool-4-thread-3一執(zhí)行第:3任務(wù)
pool-4-thread-4一執(zhí)行第:54任務(wù)
pool-4-thread-5一執(zhí)行第:55任務(wù)
pool-4-thread-6一執(zhí)行第:56任務(wù)
pool-4-thread-7一執(zhí)行第:57任務(wù)
pool-4-thread-8一執(zhí)行第:58任務(wù)
pool-4-thread-9一執(zhí)行第:59任務(wù)
pool-4-thread-10一執(zhí)行第:60任務(wù)
pool-4-thread-11一執(zhí)行第:61任務(wù)
pool-4-thread-12一執(zhí)行第:62任務(wù)
pool-4-thread-3一執(zhí)行第:4任務(wù)
pool-4-thread-2一執(zhí)行第:6任務(wù)
pool-4-thread-1一執(zhí)行第:7任務(wù)
pool-4-thread-4一執(zhí)行第:5任務(wù)
pool-4-thread-8一執(zhí)行第:8任務(wù)
pool-4-thread-7一執(zhí)行第:9任務(wù)
pool-4-thread-6一執(zhí)行第:10任務(wù)
pool-4-thread-5一執(zhí)行第:11任務(wù)
pool-4-thread-12一執(zhí)行第:12任務(wù)
pool-4-thread-11一執(zhí)行第:13任務(wù)
pool-4-thread-10一執(zhí)行第:14任務(wù)
......
pool-4-thread-2一執(zhí)行第:29381082任務(wù)
pool-4-thread-4一執(zhí)行第:29381081任務(wù)
pool-4-thread-8一執(zhí)行第:29385303任務(wù)
...

看到執(zhí)行結(jié)果的朋友們都驚呆了,為什么這執(zhí)行順序1,2,3執(zhí)行的一下子跳到54個(gè)任務(wù),然后處理完62的任務(wù)又執(zhí)行4以下的任務(wù),接下來的順序就正常了

原因: 定義線程池是核心為3個(gè)線程,所以執(zhí)行了1-3任務(wù),沒問題!到后面線程pool-4-thread-(4-12)都是 maximumPoolSize - 核心線程 = 要?jiǎng)?chuàng)建的臨時(shí)線程數(shù).這里是 重點(diǎn) ,這些臨時(shí)線程是當(dāng)你工作隊(duì)列滿了的情況才創(chuàng)建出來的, 而工作隊(duì)列里面的任務(wù)是不先處理,而且讓工作隊(duì)列存放不下的任務(wù),直接交給臨時(shí)線程處理,所以臨時(shí)線程直接執(zhí)行了54-62任務(wù),然后處理完之后在去隊(duì)列里面從頭獲取任務(wù)執(zhí)行,所以執(zhí)行完62任務(wù)后后面的執(zhí)行順序就正常了.運(yùn)行到了下面,直接任務(wù)數(shù)到了29381082時(shí),這里我是啟動(dòng)了任務(wù)執(zhí)行處理不來是或工作隊(duì)列滿了時(shí),任務(wù)丟棄了,所以存到工作隊(duì)列任務(wù)的數(shù)字就是29381082,然后就通過線程去獲取任務(wù)指定任務(wù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末窟赏,一起剝皮案震驚了整個(gè)濱河市岩灭,隨后出現(xiàn)的幾起案子拌倍,更是在濱河造成了極大的恐慌,老刑警劉巖噪径,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柱恤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡找爱,警方通過查閱死者的電腦和手機(jī)梗顺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來车摄,“玉大人寺谤,你說我怎么就攤上這事珍德∪已悖” “怎么了潮尝?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵药薯,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我敞贡,道長(zhǎng),這世上最難降的妖魔是什么摄职? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任誊役,我火速辦了婚禮,結(jié)果婚禮上谷市,老公的妹妹穿的比我還像新娘蛔垢。我一直安慰自己,他們只是感情好迫悠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布鹏漆。 她就那樣靜靜地躺著,像睡著了一般创泄。 火紅的嫁衣襯著肌膚如雪艺玲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天鞠抑,我揣著相機(jī)與錄音饭聚,去河邊找鬼。 笑死搁拙,一個(gè)胖子當(dāng)著我的面吹牛秒梳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播箕速,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼酪碘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了弧满?” 一聲冷哼從身側(cè)響起婆跑,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎庭呜,沒想到半個(gè)月后滑进,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡募谎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年扶关,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片数冬。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡节槐,死狀恐怖搀庶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铜异,我是刑警寧澤哥倔,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站揍庄,受9級(jí)特大地震影響咆蒿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚂子,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一沃测、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧食茎,春花似錦蒂破、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哎媚,卻和暖如春挟秤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抄伍。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工艘刚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人截珍。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓攀甚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親岗喉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秋度,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359