JAVA中的線程池

一云芦、為什么要使用線程池

構(gòu)建服務(wù)器應(yīng)用的簡單模型是:每當(dāng)一個請求到達(dá)就創(chuàng)建一個新線程贪惹,在新線程中請求服務(wù)察蹲。在原型開發(fā)這種方法工作得很好如果部署以這種方式運行的服務(wù)器應(yīng)用程序济锄,這種方法存在嚴(yán)重不足之一是:服務(wù)器應(yīng)用程序中單任務(wù)處理的時間短聊浅,請求數(shù)大餐抢,而每當(dāng)有一個新請求就為其創(chuàng)建一個新線程,請求處理結(jié)束后還要負(fù)責(zé)銷毀線程低匙,這大大增加了系統(tǒng)在創(chuàng)建和銷毀線程上時間的花費旷痕,還消耗了系統(tǒng)資源,往往比處理用戶實際請求的時間和資源更多顽冶。
線程池為線程生命開銷問題和資源不足問題提供了解決方案欺抗。多個任務(wù)重用線程,線程創(chuàng)建的開銷被分配到了多個任務(wù)上强重。當(dāng)請求到達(dá)時線程已經(jīng)存在绞呈,消除了線程創(chuàng)建帶來的延遲。而且通過適當(dāng)調(diào)整線程池中線程數(shù)量间景,當(dāng)請求超過閾值時佃声,強制其他新到的請求等待,直到獲得一個線程來處理倘要,防止資源不足

二圾亏、線程池的種類及區(qū)別

ThreadPoolExecutor類屬性

public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
        this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
        this.mainLock = new ReentrantLock();
        this.workers = new HashSet();
        this.termination = this.mainLock.newCondition();
        if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
            if (var6 != null && var7 != null && var8 != null) {
                this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
                this.corePoolSize = var1;
                this.maximumPoolSize = var2;
                this.workQueue = var6;
                this.keepAliveTime = var5.toNanos(var3);
                this.threadFactory = var7;
                this.handler = var8;
            } else {
                throw new NullPointerException();
            }
        } else {
            throw new IllegalArgumentException();
        }
    }

corePoolSize:核心線程數(shù);
maximumPoolSize:最大線程數(shù)封拧,線程中中允許存在的最大線程數(shù)志鹃;
keepAliveTime:線程存活時間,超過核心線程數(shù)的線程泽西,當(dāng)線程處理空閑狀態(tài)下弄跌,且維持時間達(dá)到keepAliveTime時,線程被銷毀尝苇;
unit:keepAliveTime的時間單位
workQuene:工作隊列铛只,用于存放待執(zhí)行的線程任務(wù)
threadFactory:創(chuàng)建線程的工廠,用于標(biāo)記區(qū)分不同線程池創(chuàng)建出來的線程糠溜;
handler:當(dāng)達(dá)到線程數(shù)上限或工作隊列已滿時的處理邏輯淳玩;

線程執(zhí)行策略

如果運行線程少于corePoolSize,則會創(chuàng)建一個新線程處理請求非竿,不將其添加到隊列中
如果線程數(shù)達(dá)到corePoolSize則將新的請求放入隊列中蜕着,若請求無法加入隊列,且線程數(shù)未達(dá)到maximumPoolSize則創(chuàng)建新線程,否則任務(wù)將被拒絕承匣。

BlockingQueue類型

無界隊列

隊列大小無限制蓖乘,常用的為無界的LinkedBlockingQueue,使用該隊列做為阻塞隊列時要尤其當(dāng)心韧骗,當(dāng)任務(wù)耗時較長時可能會導(dǎo)致大量新任務(wù)在隊列中堆積最終導(dǎo)致OOM嘉抒。最近工作中就遇到因為采用LinkedBlockingQueue作為阻塞隊列,部分任務(wù)耗時80s+且不停有新任務(wù)進來袍暴,導(dǎo)致cpu和內(nèi)存飆升服務(wù)器掛掉些侍。

有界隊列

常用的有兩類,一類是遵循FIFO原則的隊列如ArrayBlockingQueue與有界的LinkedBlockingQueue政模,另一類是優(yōu)先級隊列如PriorityBlockingQueue岗宣。PriorityBlockingQueue中的優(yōu)先級由任務(wù)的Comparator決定。
使用有界隊列時隊列大小需和線程池大小互相配合淋样,線程池較小有界隊列較大時可減少內(nèi)存消耗耗式,降低cpu使用率和上下文切換,但是可能會限制系統(tǒng)吞吐量趁猴。

同步移交

如果不希望任務(wù)在隊列中等待而是希望將任務(wù)直接移交給工作線程刊咳,可使用SynchronousQueue作為等待隊列。SynchronousQueue不是一個真正的隊列躲叼,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中企巢,必須有另一個線程正在等待接收這個元素枫慷。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。

1.固定大小線程池newFixedThreadPool

public class Main {

    public static void main(String[] args) {

        ExecutorService pool = Executors.newFixedThreadPool(2);

        Thread t1 = new Thread(new MyThread(1));
        Thread t2 = new Thread(new MyThread(2));
        Thread t3 = new Thread(new MyThread(3));
        Thread t4 = new Thread(new MyThread(4));

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);

        pool.shutdown();
    }

}
class MyThread implements Runnable{

    int count;

    MyThread(int i) {
        this.count = i;
    }

    public void run() {

        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("ThreadName = " + count + "----");
        }

    }

}

執(zhí)行結(jié)果:


image.png

將Executors.newFixedThreadPool(2)改成Executors.newFixedThreadPool(5)后執(zhí)行結(jié)果:


image.png

可以看出該方法指定運行線程最大數(shù)目浪规,超過這個數(shù)目線程加進去不會執(zhí)行或听,且賢臣運行順序不受加入順序影響。

2.單任務(wù)線程池笋婿,newSingleThreadExecutor

僅僅是把上述代碼中的ExecutorService pool = Executors.newFixedThreadPool(2)改為ExecutorService pool = Executors.newSingleThreadExecutor();
輸出結(jié)果:


image.png

可以看出該線程池按線程加入順序執(zhí)行誉裆,哪怕當(dāng)前線程休眠也不會跳到下一個線程執(zhí)行

3.可變尺寸線程池,newCachedThreadPool

與上面的類似缸濒,只是改動下pool的創(chuàng)建方式:ExecutorService pool = Executors.newCachedThreadPool();
執(zhí)行結(jié)果:


image.png

可根據(jù)需要創(chuàng)建新線程足丢,當(dāng)線程阻塞時會跳到其他線程執(zhí)行

三、線程池使用注意

謹(jǐn)慎使用Executors創(chuàng)建線程

Executors是java并發(fā)包提供的庇配,用于快速創(chuàng)建不同類型線程池斩跌。
Executors包中部分源碼:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }

    public static ExecutorService newCachedThreadPool(ThreadFactory var0) {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);
    }

使用Executors創(chuàng)建線程池時代碼:

ExecutorService pool = Executors.newCachedThreadPool();

        Thread t1 = new Thread(new MyThread(1));
        Thread t2 = new Thread(new MyThread(2));
        Thread t3 = new Thread(new MyThread(3));
        Thread t4 = new Thread(new MyThread(4));

        pool.execute(t1);
        pool.execute(t2);
        pool.execute(t3);
        pool.execute(t4);

        pool.shutdown();

這種方法在開發(fā)個人或臨時項目時速度很快,幾行代碼就解決捞慌,但是在大型項目中是禁止使用的耀鸦。
通過上面的源碼可以直觀地看到,它是自動地為ThreadPoolExecutor指定參數(shù)啸澡,Executors創(chuàng)建線程池時袖订,使用的是無邊界隊列SynchronousQueue氮帐,不斷加入任務(wù)會出現(xiàn)內(nèi)存溢出問題。

參考資料

Java 四種線程池的用法分析
JAVA 線程池的正確打開方式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洛姑,一起剝皮案震驚了整個濱河市上沐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吏口,老刑警劉巖奄容,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異产徊,居然都是意外死亡昂勒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門舟铜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戈盈,“玉大人,你說我怎么就攤上這事谆刨√寥ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵痊夭,是天一觀的道長刁岸。 經(jīng)常有香客問我,道長她我,這世上最難降的妖魔是什么虹曙? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮番舆,結(jié)果婚禮上酝碳,老公的妹妹穿的比我還像新娘。我一直安慰自己恨狈,他們只是感情好疏哗,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禾怠,像睡著了一般返奉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吗氏,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天衡瓶,我揣著相機與錄音,去河邊找鬼牲证。 笑死哮针,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播十厢,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼等太,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛮放?” 一聲冷哼從身側(cè)響起缩抡,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎包颁,沒想到半個月后瞻想,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡娩嚼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年蘑险,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岳悟。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡佃迄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贵少,到底是詐尸還是另有隱情呵俏,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布滔灶,位于F島的核電站普碎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏录平。R本人自食惡果不足惜麻车,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望萄涯。 院中可真熱鬧绪氛,春花似錦唆鸡、人聲如沸涝影。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽燃逻。三九已至,卻和暖如春臂痕,著一層夾襖步出監(jiān)牢的瞬間伯襟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工握童, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留姆怪,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像稽揭,于是被迫代替她去往敵國和親俺附。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

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