21.2 Java 線程的協(xié)作

多線程協(xié)作的基本機(jī)制 wait/notify

多線程之間除了競爭訪問同一個資源外,也經(jīng)常需要相互協(xié)作授滓,怎么協(xié)作呢童本?本節(jié)就來介紹Java中多線程協(xié)作的基本機(jī)制 wait/notify。

wait 實(shí)際上做了什么呢乱凿?它在等待什么蚯撩?之前我們說過础倍,每個對象都有一把鎖和等待隊列,一個線程在進(jìn)入 synchronized 代碼塊時胎挎,會嘗試獲取鎖沟启,如果獲取不到則會把當(dāng)前線程加入等待隊列中,其實(shí)犹菇,除了用于鎖的等待隊列德迹,每個對象還有另一個等待隊列,表示條件隊列揭芍,該隊列用于線程間的協(xié)作胳搞。

notify 做的事情就是從條件隊列中選一個線程,將其從隊列中移除并喚醒,notify 和 notifyAll 的區(qū)別是肌毅,它會移除條件隊列中所有的線程并全部喚醒筷转。

wait/notify 方法只能在 synchronized 代碼塊內(nèi)被調(diào)用,如果調(diào)用 wait/notify 方法時悬而,當(dāng)前線程沒有持有對象鎖呜舒,會拋出異常 java.lang.IllegalMonitor-StateException。

同時開始

每個線程在開始前進(jìn)行 wait摊滔,然后主線程通過 notifyAll 喚醒所有阴绢。

同時結(jié)束

我們之前通過主線程等待子線程使用的是 join店乐,但是 join 有時比較麻煩艰躺,需要主線程逐一等待每個子線程。

主線程先等待眨八,只有等到所有子線程結(jié)束腺兴。然后一個條件,必須先 wait廉侧,再 notify页响。

異步結(jié)果

一種常見的模式是異步調(diào)用,異步調(diào)用返回一個一般稱為 Future 的對象段誊,通過它可以獲得最終的結(jié)果闰蚕。

package qy.basic.ch21;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

/**
 * 線程池自定義任務(wù) 簡單 demo
 */
public class Ch21_10_Executor {
    
    interface MyFuture<V> {
        // 阻塞直到線程運(yùn)行結(jié)束
        V get() throws InterruptedException, ExecutionException;
    }
    
    static class MyExecutor {
        // 它封裝了創(chuàng)建子線程,同步獲取結(jié)果的過程连舍,它會創(chuàng)建一個執(zhí)行子線程
        public <V> MyFuture<V> submit(final Callable<V> callable) {
            Object lock = new Object();
            ExecutorThread<V> thread = new ExecutorThread<>(callable, lock);
            thread.start();
            
            MyFuture<V> future = new MyFuture<V>() {

                @Override
                public V get() throws InterruptedException, ExecutionException {
                    synchronized (lock) {
                        while(!thread.isDone) {
                            lock.wait();
                        }
                        if (thread.getException() != null) {
                            throw new ExecutionException(thread.getException());
                        }
                        V v = thread.getResult();
                        return v;
                    }
                }
            };
            return future;
        }
    }
    
    static class ExecutorThread<V> extends Thread {
        private V result;
        private Exception exception;
        boolean isDone = false;
        private Callable<V> callable;
        private Object lock;
        
        public ExecutorThread(Callable<V> callable, Object lock) {
            this.callable = callable;
            this.lock = lock;
        }
        
        @Override
        public void run() {
            try {
                result = callable.call();
            } catch (Exception e) {
                exception = e;
            } finally {
                synchronized (lock) {
                    isDone = true;
                    lock.notifyAll();
                }
            }
        }

        public V getResult() {
            return result;
        }

        public Exception getException() {
            return exception;
        }

        public boolean isDone() {
            return isDone;
        }
    }
    
    public static void main(String[] args) throws Exception {
        MyExecutor executor = new MyExecutor();
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(2000);
                return "hello MyExecutor!";
            }
        };
        MyFuture<String> future = executor.submit(callable);
        // 獲取異步調(diào)取結(jié)果
        String result = future.get();
        System.out.println("result = " + result);
    }

}

集合點(diǎn)

各個線程先是分頭行動没陡,各自到達(dá)一個集合點(diǎn),在集合點(diǎn)需要集齊所有線程索赏,交換數(shù)據(jù)盼玄,然后再進(jìn)行下一步動作。

線程中斷

stop 方法看上去就可以停止線程潜腻,但這個方法被標(biāo)記為了過時埃儿,簡單地說,我們不應(yīng)該使用它融涣,可以忽略它童番。

在 Java 中,停止一個線程的主要機(jī)制是中斷威鹿,中斷并不是強(qiáng)迫終止一個線程剃斧,它是一種協(xié)作機(jī)制,是給線程傳遞一個取消信號专普,但是由線程來決定如何以及何時退出悯衬。

  • void interrupt()方法 :中斷線程,例如,當(dāng)線程A運(yùn)行時筋粗,線程B可以調(diào)用線程A的interrupt()方法來設(shè)置線程A的中斷標(biāo)志為 true 并立即返回策橘。設(shè)置標(biāo)志僅僅是設(shè)置標(biāo)志,線程A實(shí)際并沒有被中斷娜亿,它會繼續(xù)往下執(zhí)行丽已。如果線程處于了阻塞狀態(tài)(如線程調(diào)用了thread.sleep、thread.join买决、thread.wait沛婴、1.5中的 condition.await、以及可中斷的通道上的 I/O 操作方法后可進(jìn)入阻塞狀態(tài))督赤,這時候若線程B調(diào)用線程A的interrupt()方法嘁灯,線程A在檢查中斷標(biāo)示時如果發(fā)現(xiàn)中斷標(biāo)示為true,則會在這些阻塞方法(sleep躲舌、join丑婿、wait、1.5中的condition.await 及可中斷的通道上的 I/O 操作方法)調(diào)用處拋出 InterruptedException 異常没卸。并且在拋出異常后立即將線程的中斷標(biāo)示位清除羹奉,即重新設(shè)置為 false。拋出異常是為了線程從阻塞狀態(tài)醒過來约计,并在結(jié)束線程前讓程序員有足夠的時間來處理中斷請求

  • boolean isInterrupted()方法:檢測當(dāng)前線程是否被中斷诀拭,如果是返回 true,否則返回 false煤蚌。并不清除中斷標(biāo)志位耕挨。

public boolean isInterrupted() {
    return isInterrupted(false);
}
  • boolean interrupted()方法:檢測當(dāng)前線程是否被中斷,如果是返回 true铺然,否則返回 false俗孝。與 isInterrupted 不同的是,該方法如果發(fā)現(xiàn)當(dāng)前線程被中斷魄健,則會清除中斷標(biāo)志赋铝,并且該方法是 static 靜態(tài)方法,可以通過 Thread 類直接調(diào)用沽瘦。另外從下面的代碼可以知道革骨,在 interrupted()內(nèi)部是獲取當(dāng)前調(diào)用線程的中斷標(biāo)志而不是調(diào)用 interrupted()方法的實(shí)例對象的中斷標(biāo)志。
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

線程對中斷的反應(yīng)

interrupt()對線程的影響與線程的狀態(tài)和在進(jìn)行的IO操作有關(guān)析恋。我們主要考慮線程的狀態(tài)良哲,IO操作的影響和具體IO以及操作系統(tǒng)有關(guān),我們就不討論了助隧。線程狀態(tài)有:

? RUNNABLE:線程在運(yùn)行或具備運(yùn)行條件只是在等待操作系統(tǒng)調(diào)度筑凫。
? WAITING/TIMED_WAITING:線程在等待某個條件或超時。
? BLOCKED:線程在等待鎖,試圖進(jìn)入同步塊巍实。
? NEW/TERMINATED:線程還未啟動或已結(jié)束滓技。

RUNNABLE:如果線程在運(yùn)行中,且沒有執(zhí)行IO操作棚潦,interrupt()只是會設(shè)置線程的中斷標(biāo)志位令漂,沒有任何其他作用。

WAITING/TIMED_WAITING:線程調(diào)用join/wait/sleep方法會進(jìn)入 WAITING 或 TIMED_WAITING狀態(tài)丸边,在這些狀態(tài)時叠必,對線程對象調(diào)用interrupt()會使得該線程拋出InterruptedException。需要注意的是妹窖,拋出異常后纬朝,中斷標(biāo)志位會被清空,而不是被設(shè)置嘱吗。

捕獲到 InterruptedException玄组,通常表示希望結(jié)束該線程滔驾,線程大致有兩種處理方式:
1)向上傳遞該異常谒麦,這使得該方法也變成了一個可中斷的方法,需要調(diào)用者進(jìn)行處理哆致;
2)有些情況绕德,不能向上傳遞異常,比如 Thread 的 run 方法摊阀,它的聲明是固定的耻蛇,不能拋出任何受檢異常,這時胞此,應(yīng)該捕獲異常臣咖,進(jìn)行合適的清理操作,清理后漱牵,一般應(yīng)該調(diào)用 Thread 的 interrupt 方法設(shè)置中斷標(biāo)志位夺蛇,使得其他代碼有辦法知道它發(fā)生了中斷。

BLOCKED:如果線程在等待鎖酣胀,對線程對象調(diào)用interrupt()只是會設(shè)置線程的中斷標(biāo)志位刁赦,線程依然會處于BLOCKED狀態(tài),也就是說闻镶,interrupt()并不能使一個在等待鎖的線程真正“中斷”甚脉。

NEW/TERMINATED:如果線程尚未啟動(NEW),或者已經(jīng)結(jié)束(TERMINATED)铆农,則調(diào)用interrupt()對它沒有任何效果牺氨,中斷標(biāo)志位也不會被設(shè)置。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市猴凹,隨后出現(xiàn)的幾起案子酝豪,更是在濱河造成了極大的恐慌,老刑警劉巖精堕,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孵淘,死亡現(xiàn)場離奇詭異,居然都是意外死亡歹篓,警方通過查閱死者的電腦和手機(jī)瘫证,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庄撮,“玉大人背捌,你說我怎么就攤上這事《此梗” “怎么了毡庆?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長烙如。 經(jīng)常有香客問我么抗,道長,這世上最難降的妖魔是什么亚铁? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任蝇刀,我火速辦了婚禮,結(jié)果婚禮上徘溢,老公的妹妹穿的比我還像新娘吞琐。我一直安慰自己,他們只是感情好然爆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布站粟。 她就那樣靜靜地躺著,像睡著了一般曾雕。 火紅的嫁衣襯著肌膚如雪奴烙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天翻默,我揣著相機(jī)與錄音缸沃,去河邊找鬼。 笑死修械,一個胖子當(dāng)著我的面吹牛趾牧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播肯污,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼翘单,長吁一口氣:“原來是場噩夢啊……” “哼吨枉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起哄芜,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤貌亭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后认臊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圃庭,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年失晴,在試婚紗的時候發(fā)現(xiàn)自己被綠了剧腻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡涂屁,死狀恐怖书在,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拆又,我是刑警寧澤儒旬,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站帖族,受9級特大地震影響栈源,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜盟萨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一凉翻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捻激,春花似錦、人聲如沸前计。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽男杈。三九已至丈屹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間伶棒,已是汗流浹背旺垒。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肤无,地道東北人先蒋。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像宛渐,于是被迫代替她去往敵國和親竞漾。 傳聞我的和親對象是個殘疾皇子眯搭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

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