線程池原理與鎖的深度化(第五天)

線程池

什么事線程池

Java中的線程池是運用場景最多的并發(fā)框架娶桦,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池泞边。在開發(fā)過程中馍资,合理地使用線程池能夠帶來3個好處穴店。

  1. 降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗迈嘹。
  2. 提高響應(yīng)速度削彬。當任務(wù)到達時,任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行秀仲。
  3. 提高線程的可管理性融痛。線程是稀缺資源,如果無限制地創(chuàng)建神僵,不僅會消耗系統(tǒng)資源雁刷,還會降低系統(tǒng)的穩(wěn)定性,使用線程池可以進行統(tǒng)一分配保礼、調(diào)優(yōu)和監(jiān)控沛励。但是,要做到合理利用線程池炮障,必須對其實現(xiàn)原理了如指掌目派。

線程池的作用

線程池是為突然大量爆發(fā)的線程設(shè)計的,通過有限的幾個固定線程為大量的操作服務(wù)胁赢,減少了創(chuàng)建和銷毀線程所需的時間企蹭,從而提高效率。
如果一個線程的時間非常長,就沒必要用線程池了(不是不能作長時間操作谅摄,而是不宜徒河。),況且我們還不能控制線程池中線程的開始螟凭、掛起虚青、和中止它呀。

線程池的分類

ThreadPoolExecutor

Executor框架的最頂層實現(xiàn)是ThreadPoolExecutor類螺男,Executors工廠類中提供的newScheduledThreadPool、newFixedThreadPool纵穿、newCachedThreadPool方法其實也只是ThreadPoolExecutor的構(gòu)造函數(shù)參數(shù)不同而已下隧。通過傳入不同的參數(shù),就可以構(gòu)造出適用于不同應(yīng)用場景下的線程池谓媒。

  1. corePoolSize: 核心池的大小淆院。 當有任務(wù)來之后,就會創(chuàng)建一個線程去執(zhí)行任務(wù)句惯,當線程池中的線程數(shù)目達到corePoolSize后土辩,就會把到達的任務(wù)放到緩存隊列當中。
  2. maximumPoolSize: 線程池最大線程數(shù)抢野,它表示在線程池中最多能創(chuàng)建多少個線程拷淘。
  3. keepAliveTime: 表示線程沒有任務(wù)執(zhí)行時最多保持多久時間會終止。
  4. unit: 參數(shù)keepAliveTime的時間單位指孤,有7種取值启涯,在TimeUnit類中有7種靜態(tài)屬性。

創(chuàng)建線程池的四中方式

  1. newCachedThreadPool創(chuàng)建一個可緩存線程池恃轩,如果線程池長度超過處理需要结洼,可靈活回收空閑線程,若無可回收叉跛,則新建線程松忍。
  2. newFixedThreadPool 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù)筷厘,超出的線程會在隊列中等待鸣峭。
  3. newScheduledThreadPool 創(chuàng)建一個定長線程池,支持定時及周期性任務(wù)執(zhí)行敞掘。
  4. newSingleThreadExecutor 創(chuàng)建一個單線程化的線程池叽掘,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行玖雁。
package top.nightliar.study.day05;

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

/**
 * 線程池的四種創(chuàng)建方法
 * Created by Nightliar
 * 2018-09-20 10:46
 */
public class ThreadPoolDemo01 {

    public static void main(String[] args) {
        // 1. 可緩存更扁,無限大小的線程池
        TestCachedThreadPool();
        // 2. 定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待
        TestFixedThreadPool();
        // 3. 創(chuàng)建一個定長線程池浓镜,支持定時及周期性任務(wù)執(zhí)行
        TestScheduledThreadPool();
        // 4. 單線程化的線程池溃列,它只會用唯一的工作線程來執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行
        TestSingleThreadExecutor();
    }

    private static void TestCachedThreadPool(){
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            int temp = i;   // 這里如果不更改temp的值膛薛,那么jvm中默認temp為final類型
            cachedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "听隐,i:" + temp));
        }
        cachedThreadPool.shutdown();
    }

    private static void TestFixedThreadPool(){
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            int temp = i;   // 這里如果不更改temp的值,那么jvm中默認temp為final類型
            fixedThreadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "哄啄,i:" + temp));
        }
        fixedThreadPool.shutdown();
    }

    private static void TestScheduledThreadPool(){
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            // 表示3秒后一起執(zhí)行
            scheduledThreadPool.schedule(() -> System.out.println(Thread.currentThread().getName() + "雅任,i:" + temp), 3, TimeUnit.SECONDS);
        }
        scheduledThreadPool.shutdown();
    }


    private static void TestSingleThreadExecutor(){
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            // 單線程執(zhí)行
            singleThreadExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ",i:" + temp);
                try {
                    Thread.sleep(200);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        singleThreadExecutor.shutdown();
    }
}

線程池的原理

提交一個任務(wù)到線程池中咨跌,線程池的處理流程如下:

  1. 判斷線程池里的核心線程是否都在執(zhí)行任務(wù)沪么,如果不是(核心線程空閑或者還有核心線程沒有被創(chuàng)建)則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)。如果核心線程都在執(zhí)行任務(wù)锌半,則進入下個流程禽车。
  2. 線程池判斷工作隊列是否已滿,如果工作隊列沒有滿刊殉,則將新提交的任務(wù)存儲在這個工作隊列里殉摔。如果工作隊列滿了,則進入下個流程记焊。
  3. 判斷線程池里的線程是否都處于工作狀態(tài)逸月,如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務(wù)亚亲。如果已經(jīng)滿了彻采,則交給飽和策略來處理這個任務(wù)。

合理配置線程池

要想合理的配置線程池捌归,就必須首先分析任務(wù)特性肛响,可以從以下幾個角度來進行分析:

  • 任務(wù)的性質(zhì):CPU密集型任務(wù),IO密集型任務(wù)和混合型任務(wù)惜索。
  • 任務(wù)的優(yōu)先級:高特笋,中和低。
  • 任務(wù)的執(zhí)行時間:長巾兆,中和短猎物。
  • 任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接角塑。
  1. CPU密集型時蔫磨,任務(wù)可以少配置線程數(shù),大概和機器的cpu核數(shù)相當圃伶,這樣可以使得每個線程都在執(zhí)行任務(wù)
  2. IO密集型時堤如,大部分線程都阻塞蒲列,故需要多配置線程數(shù),2*cpu核數(shù)

某些進程花費了絕大多數(shù)時間在計算上搀罢,而其他則在等待I/O上花費了大多是時間蝗岖,前者稱為計算密集型(CPU密集型)computer-bound,后者稱為I/O密集型榔至,I/O-bound抵赢。

Java鎖的深度化

悲觀鎖

每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖唧取,這樣別人想拿這個數(shù)據(jù)就會block直到它拿到鎖铅鲤。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖兵怯,表鎖等彩匕,讀鎖腔剂,寫鎖等媒区,都是在做操作之前先上鎖。
Select * from xxx for update;
悲觀鎖:悲觀鎖悲觀的認為每一次操作都會造成更新丟失問題掸犬,在每次查詢時加上排他鎖袜漩。

樂觀鎖

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制湾碎。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫的鎖機制實現(xiàn)宙攻,以保證操作最大程度的獨占性。但隨之而來的就是數(shù)據(jù)庫性能的大量開銷介褥,特別是對長事務(wù)而言座掘,這樣的開銷往往無法承受。而樂觀鎖機制在一定程度上解決了這個問題柔滔。樂觀鎖溢陪,大多是基于數(shù)據(jù)版本( Version )記錄機制實現(xiàn)。何謂數(shù)據(jù)版本睛廊?即為數(shù)據(jù)增加一個版本標識形真,在基于數(shù)據(jù)庫表的版本解決方案中,一般是通過為數(shù)據(jù)庫表增加一個 “version” 字段來實現(xiàn)超全。讀取出數(shù)據(jù)時咆霜,將此版本號一同讀出,之后更新時嘶朱,對此版本號加一蛾坯。此時,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫表對應(yīng)記錄的當前版本信息進行比對疏遏,如果提交的數(shù)據(jù)版本號大于數(shù)據(jù)庫表當前版本號脉课,則予以更新挂疆,否則認為是過期數(shù)據(jù)。

重入鎖

鎖作為并發(fā)共享數(shù)據(jù)下翎,保證一致性的工具缤言,在JAVA平臺有多種實現(xiàn)(如 synchronized 和 ReentrantLock等等 ) 。這些已經(jīng)寫好提供的鎖為我們開發(fā)提供了便利视事。
重入鎖胆萧,也叫做遞歸鎖,指的是同一線程 外層函數(shù)獲得鎖之后 俐东,內(nèi)層遞歸函數(shù)仍然有獲取該鎖的代碼跌穗,但不受影響。

package top.nightliar.study.day05;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 重入鎖
 * Created by Nightliar
 * 2018-09-20 16:14
 */
public class LockDemo01 {

    public static void main(String[] args) {
        Syn syn = new Syn();
        new Thread(syn).start();
        new Thread(syn).start();
        new Thread(syn).start();
        Lok lok = new Lok();
        new Thread(lok).start();
        new Thread(lok).start();
        new Thread(lok).start();
    }
}

class Syn implements Runnable {

    @Override
    public void run() {
        get();
    }

    private synchronized void get() {
        System.out.println(Thread.currentThread().getName() + ", get");
        set();
    }

    private synchronized void set() {
        System.out.println(Thread.currentThread().getName() + ", set");
    }
}

class Lok implements Runnable {

    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        get();
    }

    private void get() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + ", get");
        set();
        lock.unlock();
    }

    private void set() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + ", set");
        lock.unlock();
    }
}

讀寫鎖

相比Java中的鎖(Locks in Java)里Lock實現(xiàn)虏辫,讀寫鎖更復(fù)雜一些蚌吸。假設(shè)你的程序中涉及到對一些共享資源的讀和寫操作,且寫操作沒有讀操作那么頻繁砌庄。在沒有寫操作的時候羹唠,兩個線程同時讀一個資源沒有任何問題,所以應(yīng)該允許多個線程能在同時讀取共享資源娄昆。但是如果有一個線程想去寫這些共享資源佩微,就不應(yīng)該再有其它線程對該資源進行讀或?qū)?/strong>(注:也就是說:讀-讀能共存,讀-寫不能共存萌焰,寫-寫不能共存)哺眯。這就需要一個讀/寫鎖來解決這個問題。Java5在java.util.concurrent包中已經(jīng)包含了讀寫鎖扒俯。

package top.nightliar.study.day05;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 讀寫鎖
 * Created by Nightliar
 * 2018-09-20 16:26
 */
public class LockDemo02 {

    public static void main(String[] args) {
        new Thread(() ->{
            for (int i = 0; i < 10; i++) {
                Cache.set(i+"", i+"");
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                Cache.get(i+"");
            }
        }).start();
    }
}

class Cache {

    static volatile Map<String, Object> map = new HashMap<>();

    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();

    /**
     * 讀
     */
    public static Object get(String key){
        r.lock();
        Object obj = null;
        try {
            System.out.println("正在做讀的操作,key:" + key + " 開始");
            Thread.sleep(100);
            obj = map.get(key);
            System.out.println("正在做讀的操作,key:" + key + " 結(jié)束奶卓,value="+ obj);
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            r.unlock();
        }
        return obj;
    }

    /**
     * 寫
     */
    public static Object set(String key, Object value){
        w.lock();
        Object obj = value;
        try {
            System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "開始.");
            Thread.sleep(100);
            obj = map.put(key, value);  // 返回舊有的value
            System.out.println("正在做寫的操作,key:" + key + ",value:" + value + "結(jié)束.");
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            w.unlock();
        }
        return obj;
    }
}

CAS無鎖機制

  1. 與鎖相比,使用比較交換(下文簡稱CAS)會使程序看起來更加復(fù)雜一些撼玄。但由于其非阻塞性夺姑,它對死鎖問題天生免疫,并且互纯,線程間的相互影響也遠遠比基于鎖的方式要小瑟幕。更為重要的是,使用無鎖的方式完全沒有鎖競爭帶來的系統(tǒng)開銷留潦,也沒有線程間頻繁調(diào)度帶來的開銷只盹,因此,它要比基于鎖的方式擁有更優(yōu)越的性能兔院。
  2. 無鎖的好處:第一殖卑,在高并發(fā)的情況下,它比有鎖的程序擁有更好的性能坊萝;第二孵稽,它天生就是死鎖免疫的许起。
  3. CAS算法的過程:它包含三個參數(shù)CAS(V,E,N): V表示要更新的變量,E表示預(yù)期值菩鲜,N表示新值园细。僅當V值等于E值時,才會將V的值設(shè)為N接校,如果V值和E值不同猛频,則說明已經(jīng)有其他線程做了更新,則當前線程什么都不做蛛勉。最后鹿寻,CAS返回當前V的真實值。
  4. CAS操作是抱著樂觀的態(tài)度進行的诽凌,它總是認為自己可以成功完成操作毡熏。當多個線程同時使用CAS操作一個變量時,只有一個會勝出侣诵,并成功更新痢法,其余均會失敗。失敗的線程不會被掛起窝趣,僅是被告知失敗疯暑,并且允許再次嘗試,當然也允許失敗的線程放棄操作哑舒。基于這樣的原理幻馁,CAS操作即使沒有鎖洗鸵,也可以發(fā)現(xiàn)其他線程對當前線程的干擾,并進行恰當?shù)奶幚怼?/li>
  5. 簡單地說仗嗦,CAS需要你額外給出一個期望值膘滨,也就是你認為這個變量現(xiàn)在應(yīng)該是什么樣子的。如果變量不是你想象的那樣稀拐,那說明它已經(jīng)被別人修改過了火邓。你就重新讀取,再次嘗試修改就好了德撬。
  6. 在硬件層面铲咨,大部分的現(xiàn)代處理器都已經(jīng)支持原子化的CAS指令。在JDK 5.0以后蜓洪,虛擬機便可以使用這個指令來實現(xiàn)并發(fā)操作和并發(fā)數(shù)據(jù)結(jié)構(gòu)纤勒,并且,這種操作在虛擬機中可以說是無處不在隆檀。

自旋鎖

自旋鎖是采用讓當前線程不停地的在循環(huán)體內(nèi)執(zhí)行實現(xiàn)的摇天,當循環(huán)的條件被其他線程改變時 才能進入臨界區(qū)粹湃。
自旋鎖只是將當前線程不停地執(zhí)行循環(huán)體,不進行線程狀態(tài)的改變泉坐,所以響應(yīng)速度更快为鳄。但當線程數(shù)不停增加時,性能下降明顯腕让,因為每個線程都需要執(zhí)行济赎,占用CPU時間。如果線程競爭不激烈记某,并且保持鎖的時間段司训。適合使用自旋鎖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末液南,一起剝皮案震驚了整個濱河市壳猜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌滑凉,老刑警劉巖统扳,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畅姊,居然都是意外死亡咒钟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門若未,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朱嘴,“玉大人,你說我怎么就攤上這事粗合∑兼遥” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵隙疚,是天一觀的道長壤追。 經(jīng)常有香客問我,道長供屉,這世上最難降的妖魔是什么行冰? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮伶丐,結(jié)果婚禮上悼做,老公的妹妹穿的比我還像新娘。我一直安慰自己撵割,他們只是感情好贿堰,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啡彬,像睡著了一般羹与。 火紅的嫁衣襯著肌膚如雪故硅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天纵搁,我揣著相機與錄音吃衅,去河邊找鬼。 笑死腾誉,一個胖子當著我的面吹牛颠放,可吹牛的內(nèi)容都是我干的纺腊。 我是一名探鬼主播范嘱,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼糟红,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了猪贪?” 一聲冷哼從身側(cè)響起跷敬,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎热押,沒想到半個月后西傀,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡桶癣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年拥褂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牙寞。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡饺鹃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出碎税,到底是詐尸還是另有隱情尤慰,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布雷蹂,位于F島的核電站,受9級特大地震影響杯道,放射性物質(zhì)發(fā)生泄漏匪煌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一党巾、第九天 我趴在偏房一處隱蔽的房頂上張望萎庭。 院中可真熱鬧,春花似錦齿拂、人聲如沸驳规。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吗购。三九已至医男,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捻勉,已是汗流浹背镀梭。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留踱启,地道東北人报账。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像埠偿,于是被迫代替她去往敵國和親透罢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

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