并發(fā)編程基礎(chǔ)

本文內(nèi)容提要:wait()呻粹、notify()、join()昼扛、sleep()寸齐、yield()、interrupt()、ThreadLocal渺鹦、InheritThreadLocal扰法、TransmittableThreadLocal。

Thread的生命周期

Thread的生命周期分為初始化毅厚,就緒塞颁,運(yùn)行,阻塞吸耿,終止祠锣,其中只有運(yùn)行狀態(tài)的線程擁有CPU資源的時(shí)間片。

線程的生命周期

Object-線程的wait()和notify()

? 線程的等待和通知方法放在Object類里而非Thread類珍语,對于wait()方法來說锤岸,必須在調(diào)用之前獲取對應(yīng)實(shí)例的監(jiān)視器鎖,否則會(huì)拋出IllegalMonitorStateException板乙。而通常是偷,鎖資源可以是任意對象,把wait()募逞、notify()蛋铆、notifyAll()方法放在Obejct方法里,符合Java把所有類都會(huì)使用的方法定義在Object類的思想放接。

? 注意:正如前文所提:調(diào)用wait()之前刺啦,必須在調(diào)用之前獲取對應(yīng)實(shí)例的監(jiān)視器鎖。

 private static void interruptTest() throws InterruptedException {
        Integer obj = 1;
        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("this is begining");
                try {
                  synchronized (obj) {
                        obj.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("this is ending");
            }
        });
            a.start();
        a.join();
 }

? 當(dāng)線程調(diào)用wait方法后纠脾,會(huì)釋放鎖資源玛瘸,并進(jìn)入阻塞狀態(tài)。等待其它線程調(diào)用notify()方法苟蹈、或者notifyAll()方法喚醒糊渊,或者interrupt中斷和wait(time)調(diào)用后等待超時(shí)的虛假喚醒。當(dāng)調(diào)用notify()函數(shù)慧脱,且對于鎖對象obj渺绒,存在多個(gè)線程處于阻塞狀態(tài),會(huì)隨機(jī)選一個(gè)進(jìn)行喚醒菱鸥。而notifyAll()則會(huì)喚醒obj下所有阻塞的對象宗兼。注意:喚醒并不代表立刻執(zhí)行,而是競爭鎖氮采,競爭到鎖后才會(huì)到就緒狀態(tài)殷绍,只有等到競爭到CPU資源也就是時(shí)間片后才變成運(yùn)行狀態(tài)繼續(xù)執(zhí)行。

? 上述的運(yùn)行->阻塞->就緒->執(zhí)行的狀態(tài)轉(zhuǎn)換涉及到一個(gè)細(xì)節(jié)鹊漠,就是線程如何知道再次執(zhí)行時(shí)從哪里開始繼續(xù)往下執(zhí)行篡帕,因此會(huì)在阻塞時(shí)殖侵,或者說進(jìn)行時(shí)間片切換時(shí),記錄當(dāng)前執(zhí)行地址镰烧,這里用到的是線程私有的程序計(jì)數(shù)器

Thread里的方法

等待線程終止的join()方法

? 有時(shí)候存在這樣的需求楞陷,主線程開啟n個(gè)子線程怔鳖,并希望在所有子線程結(jié)束后在進(jìn)行一些邏輯操作。這時(shí)候就需要用到j(luò)oin()方法固蛾。

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Thread a = new Thread(() -> {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("subThread is over");
    });

    a.start();
    a.join();
    System.out.println("main is over");
}

輸出結(jié)果為:

subThread is over
main is over

主線程在調(diào)用了a線程后進(jìn)入阻塞狀態(tài)结执,這時(shí)可以通過interrupt()方法中斷阻塞狀態(tài)。

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Thread a = new Thread(() -> {
        while (true) {
        }
    });

    Thread b = new Thread(() -> {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        mainThread.interrupt();
    });

    b.start();
    a.start();
    try {
        a.join();
    } catch (InterruptedException e) {
        System.out.println("main is interrupted");
    }
    System.out.println("main is over");
}

輸出結(jié)果為:

main is interrupted
main is over

讓線程睡眠的sleep()方法

sleep()方法會(huì)讓當(dāng)前線程進(jìn)入阻塞狀態(tài)艾凯,但不會(huì)釋放鎖資源献幔。

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Integer lock1 = 1, lock2 = 1;
    Thread a = new Thread(() -> {
        synchronized (lock1) {
            System.out.println("a get lock1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock2) {
                System.out.println("a get lock2");
            }
        }
    });
    Thread b = new Thread(() -> {
        synchronized (lock1) {
            System.out.println("b get lock1");
            synchronized (lock2) {
                System.out.println("b get lock2");
            }
        }
    });
    a.start();
    b.start();
    a.join();b.join();
    System.out.println("main is over");
}

? 在上面的例子中,由于a線程先獲取到鎖資源lock1趾诗,即使a調(diào)用sleep()方法進(jìn)入阻塞狀態(tài)蜡感,b線程仍然無法獲取到鎖資源lock1(即lock1在a線程sleep()之后并沒有被釋放)。注意:sleep()入?yún)⒉荒転樨?fù)數(shù)恃泪,會(huì)拋出異常郑兴。

讓出CPU時(shí)間片的yield()方法

? yield()方法調(diào)用后會(huì)暗示線程調(diào)度器希望讓出當(dāng)前線程所占的時(shí)間片,但是線程調(diào)度器可以無條件忽略這個(gè)暗示贝乎。如果yield()方法成功讓出CPU時(shí)間片情连,就會(huì)進(jìn)入就緒狀態(tài),等待重新競爭到時(shí)間片繼續(xù)執(zhí)行览效。所以却舀,存在這樣的情況線程A在調(diào)用yield()方法后,通過競爭在下一輪線程調(diào)度中再次獲取到了時(shí)間片锤灿。同樣的挽拔,yield()方法不會(huì)讓出鎖資源,下面的demo可以證明即使a線程yield()衡招,b線程獲取到時(shí)間片開始執(zhí)行篱昔,仍然無法獲取到lock1資源,所以輸出結(jié)果仍然是先執(zhí)行完a線程始腾。

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Integer lock1 = 1, lock2 = 1;
    Thread a = new Thread(() -> {
        synchronized (lock1) {
            System.out.println("a get lock1");
            Thread.yield();
            synchronized (lock2) {
                System.out.println("a get lock2");
            }
        }
    });
    Thread b = new Thread(() -> {
        System.out.println("b get cpu!");
        synchronized (lock1) {
            System.out.println("b get lock1");
            synchronized (lock2) {
                System.out.println("b get lock2");
            }
        }
    });
    a.start();
    b.start();
    a.join();
    b.join();
    System.out.println("main is over");
}

輸出結(jié)果為:

a get lock1
b get cpu!
a get lock2
b get lock1
b get lock2
main is over

如果將yield()替換為wait()州刽,a線程進(jìn)入阻塞狀態(tài)后,釋放資源浪箭,b線程成功獲取到lock1鎖資源穗椅,輸出結(jié)果證明他們的差異。

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Integer lock1 = 1, lock2 = 1;
    Thread a = new Thread(() -> {
        synchronized (lock1) {
            System.out.println("a get lock1");
            try {
                lock1.wait();
            } catch (InterruptedException e) {
                System.out.println("a is interrupted");
            }
            synchronized (lock2) {
                System.out.println("a get lock2");
            }
        }
    });
    Thread b = new Thread(() -> {
        System.out.println("b get cpu!");
        synchronized (lock1) {
            System.out.println("b get lock1");
            synchronized (lock2) {
                System.out.println("b get lock2");
            }
            a.interrupt();
        }
    });
    a.start();
    b.start();
    a.join();
    b.join();
    System.out.println("main is over");
}

輸出結(jié)果:

a get lock1
b get cpu!
b get lock1
b get lock2
a is interrupted
a get lock2
main is over

設(shè)置中斷標(biāo)志的interrupt()方法

? 前文的最佳配角interrupt()方法奶栖,并非暴力地直接中斷對應(yīng)的線程匹表,而是對對應(yīng)的線程設(shè)置中斷標(biāo)志门坷。

// 檢測當(dāng)前實(shí)例線程是否被中斷,中斷true袍镀,否則false
private native boolean isInterrupted(boolean ClearInterrupted);

// 檢測當(dāng)前線程是否被中斷默蚌,如果發(fā)現(xiàn)線程被中斷,會(huì)清除中斷標(biāo)志苇羡,返回true绸吸。否則返回false
private native boolean interrupted(){
        return currentThread().isInterrupted(true);
}

// 設(shè)置中斷標(biāo)志位true
public void interrupt();

interrupted()檢測的是當(dāng)前線程

這里要注意的是interrupted()檢測的是當(dāng)前線程,跟句柄無關(guān)设江。如下面的demo:

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Thread a = new Thread(() -> {
        while (true) {

        }
    });
    a.start();
    a.interrupt();
    System.out.println();
    System.out.println("is interrupted :" + a.isInterrupted()); // 1
    System.out.println("is interrupted :" + a.interrupted());  // 2
    System.out.println("is interrupted :" + a.isInterrupted()); // 3
}

輸出結(jié)果:

true
false
true

2處雖然句柄為a線程锦茁,但是正如前文所述,在interrupted()方法中會(huì)調(diào)用Thread.getCurrentThread()方法獲取當(dāng)前線程叉存,獲取到線程為主線程码俩,而主線程并未被中斷,所以輸出false歼捏。

interrupt() 只是設(shè)置中斷標(biāo)志稿存,并非直接中斷

public static void main(String[] args) throws InterruptedException {

    final Thread mainThread = Thread.currentThread();
    Thread a = new Thread(() -> {
        while (true) {
            System.out.println("a is working");
        }
    });
    a.start();
    a.interrupt();
    a.join();
}

輸出結(jié)果:

a is working
a is working
a is working
a is working
a is working
...

可以發(fā)現(xiàn)如果a在內(nèi)部沒有調(diào)用wait、sleep等方法進(jìn)入阻塞狀態(tài)甫菠,就不會(huì)被中斷挠铲。

ThreadLocal — 你不得不知道的坑

? ThreadLocal只能在保證當(dāng)前線程可以獲取到對應(yīng)的變量。

? 考慮到存在這樣的情況寂诱,主線程在ThreadLocal中放了參數(shù)拂苹,并啟用了多個(gè)子線程進(jìn)行工作,同時(shí)子線程需要用到前面主線程在ThreadLocal中放置的參數(shù)痰洒。這時(shí)候考慮到用InheritThreadLocal瓢棒,在Thread.init()方法源碼中可以看到,當(dāng)線程初始化時(shí)丘喻,InheritThreadLocal中存放的參數(shù)會(huì)被復(fù)制到子線程的InheritThreadLocal中脯宿。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }

  ...  
  ...
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;

    /* Set thread ID */
    tid = nextThreadID();
}

? 那么是否InheritThreadLocal就已經(jīng)能解決多線程問題了呢?答案是并不能泉粉。因?yàn)槲覀冎涝诰€程池的使用中连霉,為了減少線程初始化和銷毀的性能消耗,提出了線程復(fù)用的概念嗡靡。對于核心線程來說跺撼,一旦被初始化后,就不會(huì)被銷毀讨彼。對于InheritThreadLocal而言歉井,其變量的傳遞主要依賴于Thread.init()方法中進(jìn)行參數(shù)復(fù)制傳遞。

? 所以當(dāng)使用線程池時(shí)哈误,會(huì)發(fā)現(xiàn)每個(gè)線程的InheritThreadLocal中的參數(shù)哩至,一旦被賦值后就不會(huì)再更新躏嚎,也就失去了它的正確性,可以理解為是非線程安全的菩貌。這時(shí)候可以考慮使用TransmittableThreadLocal來解決卢佣,具體可見TTL項(xiàng)目的官網(wǎng)說明(如傳遞鏈路id等)。

參照

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箭阶,一起剝皮案震驚了整個(gè)濱河市珠漂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尾膊,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荞彼,死亡現(xiàn)場離奇詭異冈敛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸣皂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門抓谴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寞缝,你說我怎么就攤上這事癌压。” “怎么了荆陆?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵滩届,是天一觀的道長。 經(jīng)常有香客問我被啼,道長帜消,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任浓体,我火速辦了婚禮泡挺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘命浴。我一直安慰自己娄猫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布生闲。 她就那樣靜靜地躺著媳溺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跪腹。 梳的紋絲不亂的頭發(fā)上褂删,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音冲茸,去河邊找鬼屯阀。 笑死缅帘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的难衰。 我是一名探鬼主播钦无,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盖袭!你這毒婦竟也來了失暂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鳄虱,失蹤者是張志新(化名)和其女友劉穎弟塞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拙已,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡决记,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倍踪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片系宫。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖建车,靈堂內(nèi)的尸體忽然破棺而出扩借,到底是詐尸還是另有隱情,我是刑警寧澤缤至,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布潮罪,位于F島的核電站,受9級特大地震影響凄杯,放射性物質(zhì)發(fā)生泄漏错洁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一戒突、第九天 我趴在偏房一處隱蔽的房頂上張望屯碴。 院中可真熱鬧,春花似錦膊存、人聲如沸导而。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今艺。三九已至,卻和暖如春爵卒,著一層夾襖步出監(jiān)牢的瞬間虚缎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工钓株, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留实牡,地道東北人陌僵。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像创坞,于是被迫代替她去往敵國和親碗短。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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

  • 現(xiàn)代操作系統(tǒng)在運(yùn)行一個(gè)程序時(shí)题涨,會(huì)為其創(chuàng)建一個(gè)進(jìn)程偎谁。例如,啟動(dòng)一個(gè)Java程序纲堵,操作系統(tǒng)就會(huì)創(chuàng)建一個(gè)Java進(jìn)程巡雨。線...
    java架構(gòu)源閱讀 212評論 0 0
  • Java從誕生開始就明智地選擇了內(nèi)置對多線程的支持,這使得Java語言相比同一時(shí)期的其他語言具有明顯的優(yōu)勢席函。線程作...
    八年碼農(nóng)閱讀 228評論 0 1
  • 前言:Java從誕生開始就明智地選擇了內(nèi)置對多線程的支持鸯隅,這使得Java語言相比同一時(shí)期的其他語言具有的有事.線程...
    叫我胖虎大人閱讀 265評論 0 0
  • 1. 線程簡介 1.1 什么是線程 線程是現(xiàn)代操作系統(tǒng)能夠進(jìn)行調(diào)度和運(yùn)算的基本單位 在一個(gè)進(jìn)程中可以創(chuàng)建多個(gè)線程,...
    ygxing閱讀 328評論 0 0
  • 特別說明:文章內(nèi)容是《Java并發(fā)編程的藝術(shù)》讀書筆記 Java是一種多線程語言,從誕生開始就內(nèi)置了對多線程的支持...
    codersm閱讀 173評論 0 0