線程池你真不來了解一下嗎嗡靡?

前言

只有光頭才能變強(qiáng)

回顧前面:

本篇主要是講解線程池讨彼,這是我在多線程的倒數(shù)第二篇了,后面還會(huì)有一篇死鎖柿祈。主要將多線程的基礎(chǔ)過一遍哈误,以后有機(jī)會(huì)再繼續(xù)深入哩至!

那么接下來就開始吧,如果文章有錯(cuò)誤的地方請大家多多包涵蜜自,不吝在評(píng)論區(qū)指正哦~

聲明:本文使用JDK1.8

一菩貌、線程池簡介

線程池可以看做是線程的集合。在沒有任務(wù)時(shí)線程處于空閑狀態(tài)重荠,當(dāng)請求到來:線程池給這個(gè)請求分配一個(gè)空閑的線程箭阶,任務(wù)完成后回到線程池中等待下次任務(wù)(而不是銷毀)。這樣就實(shí)現(xiàn)了線程的重用戈鲁。

我們來看看如果沒有使用線程池的情況是這樣的:

  • 為每個(gè)請求都新開一個(gè)線程仇参!

public class ThreadPerTaskWebServer {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(80);
        while (true) {
            // 為每個(gè)請求都創(chuàng)建一個(gè)新的線程
            final Socket connection = socket.accept();
            Runnable task = () -> handleRequest(connection);
            new Thread(task).start();
        }
    }
    private static void handleRequest(Socket connection) {
        // request-handling logic here
    }
}

為每個(gè)請求都開一個(gè)新的線程雖然理論上是可以的,但是會(huì)有缺點(diǎn)

  • 線程生命周期的開銷非常高婆殿。每個(gè)線程都有自己的生命周期诈乒,創(chuàng)建和銷毀線程所花費(fèi)的時(shí)間和資源可能比處理客戶端的任務(wù)花費(fèi)的時(shí)間和資源更多,并且還會(huì)有某些空閑線程也會(huì)占用資源婆芦。
  • 程序的穩(wěn)定性和健壯性會(huì)下降抓谴,每個(gè)請求開一個(gè)線程。如果受到了惡意攻擊或者請求過多(內(nèi)存不足)寞缝,程序很容易就奔潰掉了癌压。

所以說:我們的線程最好是交由線程池來管理,這樣可以減少對線程生命周期的管理荆陆,一定程度上提高性能滩届。

二、JDK提供的線程池API

JDK給我們提供了Excutor框架來使用線程池被啼,它是線程池的基礎(chǔ)帜消。

  • Executor提供了一種將“任務(wù)提交”與“任務(wù)執(zhí)行”分離開來的機(jī)制(解耦)

下面我們來看看JDK線程池的總體api架構(gòu):

image

接下來我們把這些API都過一遍看看:

Executor接口:

image

ExcutorService接口:

image

AbstractExecutorService類:

image

ScheduledExecutorService接口:

image

ThreadPoolExecutor類:

image

ScheduledThreadPoolExecutor類:

image

2.1ForkJoinPool線程池

除了ScheduledThreadPoolExecutor和ThreadPoolExecutor類線程池以外,還有一個(gè)是JDK1.7新增的線程池:ForkJoinPool線程池

于是我們的類圖就可以變得完整一些:

image

JDK1.7中新增的一個(gè)線程池浓体,與ThreadPoolExecutor一樣泡挺,同樣繼承了AbstractExecutorService。ForkJoinPool是Fork/Join框架的兩大核心類之一命浴。與其它類型的ExecutorService相比娄猫,其主要的不同在于采用了工作竊取算法(work-stealing):所有池中線程會(huì)嘗試找到并執(zhí)行已被提交到池中的或由其他線程創(chuàng)建的任務(wù)。這樣很少有線程會(huì)處于空閑狀態(tài)生闲,非常高效媳溺。這使得能夠有效地處理以下情景:大多數(shù)由任務(wù)產(chǎn)生大量子任務(wù)的情況;從外部客戶端大量提交小任務(wù)到池中的情況碍讯。

來源:

2.2補(bǔ)充:Callable和Future

學(xué)到了線程池悬蔽,我們可以很容易地發(fā)現(xiàn):很多的API都有Callable和Future這么兩個(gè)東西。


    Future<?> submit(Runnable task)
    <T> Future<T> submit(Callable<T> task)

其實(shí)它們也不是什么高深的東西~~~

我們可以簡單認(rèn)為:Callable就是Runnable的擴(kuò)展捉兴。

  • Runnable沒有返回值蝎困,不能拋出受檢查的異常录语,而Callable可以
image

也就是說:當(dāng)我們的任務(wù)需要返回值的時(shí)禾乘,我們就可以使用Callable澎埠!

Future一般我們認(rèn)為是Callable的返回值,但他其實(shí)代表的是任務(wù)的生命周期(當(dāng)然了盖袭,它是能獲取得到Callable的返回值的)

image

簡單來看一下他們的用法:


public class CallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 創(chuàng)建線程池對象
        ExecutorService pool = Executors.newFixedThreadPool(2);

        // 可以執(zhí)行Runnable對象或者Callable對象代表的線程
        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));

        // V get()
        Integer i1 = f1.get();
        Integer i2 = f2.get();

        System.out.println(i1);
        System.out.println(i2);

        // 結(jié)束
        pool.shutdown();
    }
}

Callable任務(wù):


public class MyCallable implements Callable<Integer> {

    private int number;

    public MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }

}

執(zhí)行完任務(wù)之后可以獲取得到任務(wù)返回的數(shù)據(jù)

image

三失暂、ThreadPoolExecutor詳解

這是用得最多的線程池彼宠,所以本文會(huì)重點(diǎn)講解它鳄虱。

我們來看看頂部注釋:

image

3.1內(nèi)部狀態(tài)

image

變量ctl定義為AtomicInteger,記錄了“線程池中的任務(wù)數(shù)量”和“線程池的狀態(tài)”兩個(gè)信息凭峡。

image

線程的狀態(tài):

  • RUNNING:線程池能夠接受新任務(wù)拙已,以及對新添加的任務(wù)進(jìn)行處理。
  • SHUTDOWN:線程池不可以接受新任務(wù)摧冀,但是可以對已添加的任務(wù)進(jìn)行處理倍踪。
  • STOP:線程池不接收新任務(wù),不處理已添加的任務(wù)索昂,并且會(huì)中斷正在處理的任務(wù)建车。
  • TIDYING:當(dāng)所有的任務(wù)已終止,ctl記錄的"任務(wù)數(shù)量"為0椒惨,線程池會(huì)變?yōu)門IDYING狀態(tài)缤至。當(dāng)線程池變?yōu)門IDYING狀態(tài)時(shí),會(huì)執(zhí)行鉤子函數(shù)terminated()康谆。terminated()在ThreadPoolExecutor類中是空的领斥,若用戶想在線程池變?yōu)門IDYING時(shí),進(jìn)行相應(yīng)的處理沃暗;可以通過重載terminated()函數(shù)來實(shí)現(xiàn)月洛。
  • TERMINATED:線程池徹底終止的狀態(tài)
image

各個(gè)狀態(tài)之間轉(zhuǎn)換:

image

3.2已默認(rèn)實(shí)現(xiàn)的池

下面我就列舉三個(gè)比較常見的實(shí)現(xiàn)池:

  • newFixedThreadPool
  • newCachedThreadPool
  • SingleThreadExecutor

如果讀懂了上面對應(yīng)的策略呀孽锥,線程數(shù)量這些嚼黔,應(yīng)該就不會(huì)太難看懂了。

3.2.1newFixedThreadPool

一個(gè)固定線程數(shù)的線程池惜辑,它將返回一個(gè)corePoolSize和maximumPoolSize相等的線程池隔崎。


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

3.2.2newCachedThreadPool

非常有彈性的線程池,對于新的任務(wù)韵丑,如果此時(shí)線程池里沒有空閑線程爵卒,線程池會(huì)毫不猶豫的創(chuàng)建一條新的線程去處理這個(gè)任務(wù)


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

3.2.3SingleThreadExecutor

使用單個(gè)worker線程的Executor


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

3.3構(gòu)造方法

我們讀完上面的默認(rèn)實(shí)現(xiàn)池還有對應(yīng)的屬性撵彻,再回到構(gòu)造方法看看

  • 構(gòu)造方法可以讓我們自定義(擴(kuò)展)線程池

    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. 指定核心線程數(shù)量
  2. 指定最大線程數(shù)量
  3. 允許線程空閑時(shí)間
  4. 時(shí)間對象
  5. 阻塞隊(duì)列
  6. 線程工廠
  7. 任務(wù)拒絕策略

再總結(jié)一遍這些參數(shù)的要點(diǎn):

線程數(shù)量要點(diǎn)

  • 如果運(yùn)行線程的數(shù)量少于核心線程數(shù)量钓株,則創(chuàng)建新的線程處理請求
  • 如果運(yùn)行線程的數(shù)量大于核心線程數(shù)量实牡,小于最大線程數(shù)量,則當(dāng)隊(duì)列滿的時(shí)候才創(chuàng)建新的線程
  • 如果核心線程數(shù)量等于最大線程數(shù)量轴合,那么將創(chuàng)建固定大小的連接池
  • 如果設(shè)置了最大線程數(shù)量為無窮创坞,那么允許線程池適合任意的并發(fā)數(shù)量

線程空閑時(shí)間要點(diǎn):

  • 當(dāng)前線程數(shù)大于核心線程數(shù),如果空閑時(shí)間已經(jīng)超過了受葛,那該線程會(huì)銷毀题涨。

排隊(duì)策略要點(diǎn)

  • 同步移交:不會(huì)放到隊(duì)列中,而是等待線程執(zhí)行它总滩。如果當(dāng)前線程沒有執(zhí)行纲堵,很可能會(huì)新開一個(gè)線程執(zhí)行。
  • 無界限策略:如果核心線程都在工作闰渔,該線程會(huì)放到隊(duì)列中席函。所以線程數(shù)不會(huì)超過核心線程數(shù)
  • 有界限策略:可以避免資源耗盡,但是一定程度上減低了吞吐量

當(dāng)線程關(guān)閉或者線程數(shù)量滿了和隊(duì)列飽和了冈涧,就有拒絕任務(wù)的情況了:

拒絕任務(wù)策略:

  • 直接拋出異常
  • 使用調(diào)用者的線程來處理
  • 直接丟掉這個(gè)任務(wù)
  • 丟掉最老的任務(wù)

四茂附、execute執(zhí)行方法

execute執(zhí)行方法分了三步,以注釋的方式寫在代碼上了~


    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果線程池中運(yùn)行的線程數(shù)量<corePoolSize督弓,則創(chuàng)建新線程來處理請求营曼,即使其他輔助線程是空閑的。
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }

        //如果線程池中運(yùn)行的線程數(shù)量>=corePoolSize愚隧,且線程池處于RUNNING狀態(tài)蒂阱,且把提交的任務(wù)成功放入阻塞隊(duì)列中,就再次檢查線程池的狀態(tài)奸攻,
            // 1.如果線程池不是RUNNING狀態(tài)蒜危,且成功從阻塞隊(duì)列中刪除任務(wù),則該任務(wù)由當(dāng)前 RejectedExecutionHandler 處理睹耐。
            // 2.否則如果線程池中運(yùn)行的線程數(shù)量為0辐赞,則通過addWorker(null, false)嘗試新建一個(gè)線程,新建線程對應(yīng)的任務(wù)為null硝训。
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 如果以上兩種case不成立响委,即沒能將任務(wù)成功放入阻塞隊(duì)列中,且addWoker新建線程失敗窖梁,則該任務(wù)由當(dāng)前 RejectedExecutionHandler 處理赘风。
        else if (!addWorker(command, false))
            reject(command);
    }

五、線程池關(guān)閉

ThreadPoolExecutor提供了shutdown()shutdownNow()兩個(gè)方法來關(guān)閉線程池

shutdown() :


image

shutdownNow():

image

區(qū)別:

  • 調(diào)用shutdown()后纵刘,線程池狀態(tài)立刻變?yōu)镾HUTDOWN邀窃,而調(diào)用shutdownNow(),線程池狀態(tài)立刻變?yōu)镾TOP假哎。
  • shutdown()等待任務(wù)執(zhí)行完才中斷線程瞬捕,而shutdownNow()不等任務(wù)執(zhí)行完就中斷了線程鞍历。

六、總結(jié)

本篇博文主要簡單地將多線程的結(jié)構(gòu)體系過了一篇肪虎,講了最常用的ThreadPoolExecutor線程池是怎么使用的~~~

明天希望可以把死鎖寫出來劣砍,敬請期待~~~

還有剩下的幾個(gè)線程池(給出了參考資料):

參考資料:

如果文章有錯(cuò)的地方歡迎指正,大家互相交流扇救。習(xí)慣在微信看技術(shù)文章刑枝,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號(hào):Java3y迅腔。為了大家方便装畅,剛新建了一下qq群:742919422,大家也可以去交流交流钾挟。謝謝支持了洁灵!希望能多介紹給其他有需要的朋友

文章的目錄導(dǎo)航

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饱岸,一起剝皮案震驚了整個(gè)濱河市掺出,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌苫费,老刑警劉巖汤锨,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異百框,居然都是意外死亡闲礼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門铐维,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柬泽,“玉大人,你說我怎么就攤上這事嫁蛇∠遣ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵睬棚,是天一觀的道長第煮。 經(jīng)常有香客問我,道長抑党,這世上最難降的妖魔是什么包警? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮底靠,結(jié)果婚禮上害晦,老公的妹妹穿的比我還像新娘。我一直安慰自己暑中,他們只是感情好壹瘟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布苟呐。 她就那樣靜靜地躺著,像睡著了一般俐筋。 火紅的嫁衣襯著肌膚如雪牵素。 梳的紋絲不亂的頭發(fā)上咖杂,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天摸吠,我揣著相機(jī)與錄音朦蕴,去河邊找鬼扁誓。 笑死檩坚,一個(gè)胖子當(dāng)著我的面吹牛钮科,可吹牛的內(nèi)容都是我干的殖妇。 我是一名探鬼主播狱从,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼询筏,長吁一口氣:“原來是場噩夢啊……” “哼榕堰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嫌套,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤逆屡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后踱讨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魏蔗,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年痹筛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了莺治。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帚稠,死狀恐怖谣旁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滋早,我是刑警寧澤榄审,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站馆衔,受9級(jí)特大地震影響瘟判,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜角溃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一拷获、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧减细,春花似錦匆瓜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茧妒。三九已至,卻和暖如春左冬,著一層夾襖步出監(jiān)牢的瞬間桐筏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工拇砰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梅忌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓除破,卻偏偏與公主長得像牧氮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子瑰枫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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