Java 線程間通信和協(xié)作的兩種方式

現(xiàn)在計算機和智能手機都是多核處理器蟹腾,為了更好地發(fā)揮設(shè)備的性能,提高應(yīng)用程序的體驗性笋鄙,多線程是必不可少的技術(shù)。線程之間不是孤立的怪瓶,它們共享進程的資源和數(shù)據(jù)萧落,彼此之間還需要進行通信和協(xié)作,最典型的例子就是「生產(chǎn)者-消費者模型」洗贰。下面先介紹 wait/notify 機制和 Lock/Condition 機制找岖,然后用兩個線程交替打印奇偶數(shù)。

1. wait/notify

wait 和 notify 是 Object 類的兩個方法敛滋,理解起來還是有些復(fù)雜的许布。它和多線程同步有關(guān)系,個人覺得放在 Object 類不太合理绎晃,可能是歷史遺留問題吧蜜唾。每個對象都有一把鎖(monitor),在進入同步方法或代碼塊之前庶艾,當(dāng)前線程需要先獲取對象鎖袁余,然后才能執(zhí)行同步塊的代碼,完成后釋放對象鎖咱揍。鎖可以理解為唯一的憑證颖榜,有了它就能入場,而且獨占所有的資源煤裙,立場就得交出去掩完。

wait 方法的作用是使當(dāng)前線程釋放對象鎖,并進入等待狀態(tài)硼砰,不再往下執(zhí)行且蓬。當(dāng)其他線程調(diào)用對象的 notify/notifyAll 時,會喚醒等待的線程题翰,等到其他線程釋放鎖后恶阴,被喚醒的現(xiàn)象將繼續(xù)往下執(zhí)行。notify 隨機喚醒一個等待的線程遍愿,notifAll 喚醒所有等待的線程存淫。注意:wait 和 notify 都需要在拿到對象鎖的情況下調(diào)用。下面是 wait 的標(biāo)準(zhǔn)使用方法(來自 《Effective Java》一書):

synchronized (obj) {
  while (condition does not hold) {
    obj.wait(); // release lock and reacquire on wakeup
    // perform action appropriate to condition
  }
}

每個鎖對象都有兩個隊列:就緒隊列和阻塞隊列沼填。就緒隊列存儲了已經(jīng)就緒(將要競爭鎖)的線程桅咆,阻塞隊列存儲了被阻塞的線程。當(dāng)阻塞線程被喚醒后坞笙,才會進入就緒隊列岩饼,然后等待 CPU 的調(diào)度荚虚;反之,當(dāng)一個線程被阻塞后籍茧,就會進入阻塞隊列版述,等待被喚醒。

舉個例子寞冯,線程 A 在執(zhí)行任務(wù)渴析,它等待線程 B 做完某個操作,才能往下執(zhí)行吮龄,這就可以用 wait/notify 實現(xiàn)俭茧。

    public void start() {
        new Thread(new TaskA()).start();
        new Thread(new TaskB()).start();
    }

    private final Object lock = new Object();
    private boolean finished;

    private class TaskA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("線程 A 拿到鎖了,開始工作");
                while (!finished) {
                    try {
                        System.out.println("線程 A 釋放了鎖漓帚,進入等待狀態(tài)");
                        lock.wait();
                        System.out.println("線程 A 收到信號母债,繼續(xù)工作");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("線程 A 釋放了鎖");
        }
    }

    private class TaskB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("線程 B 拿到了鎖,開始工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("-----------------------");
                System.out.println("線程 B 發(fā)信號了尝抖,完成工作");
                finished = true;
                lock.notify();
            }
            System.out.println("線程 B 釋放了鎖");
        }
    }
/* 打诱泵恰:
線程 A 拿到鎖了,開始工作
線程 A 釋放了鎖昧辽,進入等待狀態(tài)
線程 B 拿到了鎖衙熔,開始工作
-----------------------
線程 B 發(fā)信號了,完成工作
線程 B 釋放了鎖
線程 A 收到信號奴迅,繼續(xù)工作
線程 A 釋放了鎖  
*/

2. Lock/Condition

Condition 可以看作 Object 的 wait/notify 的替代方案青责,同樣用來實現(xiàn)線程間的協(xié)作。與使用 wait/notify 相比取具,Condition的 await/signal 更加靈活、安全和高效扁耐。Condition 是個接口暇检,基本的方法就是 await() 和 signal()。Condition 依賴于 Lock 接口婉称,生成一個 Condition 的代碼是 lock.newCondition() 块仆。 需要注意 Condition 的 await()/signal() 使用都必須在lock.lock() 和 lock.unlock() 之間才可以,Conditon 和 Object 的 wait/notify 有著天然的對應(yīng)關(guān)系:

  • Conditon 中的 await() 對應(yīng) Object 的 wait()王暗;
  • Condition 中的 signal() 對應(yīng) Object 的 notify()悔据;
  • Condition 中的 signalAll() 對應(yīng) Object 的 notifyAll();

舉個例子俗壹,使用 Condition 實現(xiàn)和上面的功能科汗。

    public void start() {
        new Thread(new TaskC()).start();
        new Thread(new TaskD()).start();
    }
    
    private Lock reentrantLock = new ReentrantLock();
    private Condition condition = reentrantLock.newCondition();

    private class TaskC implements Runnable {
        @Override
        public void run() {
            reentrantLock.lock();
            System.out.println("線程 C 拿到了鎖,開始工作");
            try {
                System.out.println("線程 C 釋放了鎖绷雏,進入等待狀態(tài)");
                condition.await();
                System.out.println("線程 C 收到信號头滔,繼續(xù)工作");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("線程 C 釋放了鎖");
                reentrantLock.unlock();
            }
        }
    }

    private class TaskD implements Runnable {
        @Override
        public void run() {
            reentrantLock.lock();
            System.out.println("線程 D 拿到了鎖怖亭,開始工作");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----------------------");
            try {
                System.out.println("線程 D 發(fā)信號了,完成工作");
                condition.signal();
            } finally {
                System.out.println("線程 D 釋放了鎖");
                reentrantLock.unlock();
            }
        }
    }
/*打永ぜ臁:
線程 C 拿到了鎖兴猩,開始工作
線程 C 釋放了鎖,進入等待狀態(tài)
線程 D 拿到了鎖早歇,開始工作
-----------------------
線程 D 發(fā)信號了倾芝,完成工作
線程 D 釋放了鎖
線程 C 收到信號,繼續(xù)工作
線程 C 釋放了鎖
*/

相比 Object 的 wait/notify箭跳,Condition 有許多優(yōu)點:

  • Condition 可以支持多個等待隊列蛀醉,因為一個 Lock 實例可以綁定多個 Condition

  • Condition 支持等待狀態(tài)下不響應(yīng)中斷

  • Condition 支持當(dāng)前線程進入等待狀態(tài),直到將來的某個時間

3. 兩個線程交替打印奇偶數(shù)

使用 wait/notify:

    public void printNumber() {
        new Thread(new EvenTask()).start();
        new Thread(new OddTask()).start();
    }
    
    private int number = 10;
    private final Object numberLock = new Object();

    private class EvenTask implements Runnable {
        @Override
        public void run() {
            synchronized (numberLock) {
                while (number >= 0 && (number & 1) == 0) {
                    System.out.println("偶數(shù): " + (number--));
                    numberLock.notify();
                    try {
                        numberLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private class OddTask implements Runnable {
        @Override
        public void run() {
            synchronized (numberLock) {
                while (number >= 0 && (number & 1) == 1) {
                    System.out.println("奇數(shù): " + (number--));
                    numberLock.notify();
                    try {
                        numberLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

使用 Lock/Condition:

    public void printNumber() {
        new Thread(new EvenTask()).start();
        new Thread(new OddTask()).start();
    }
    
    private int number = 10;
    private Condition evenCondition = reentrantLock.newCondition();
    private Condition oddCondition = reentrantLock.newCondition();

    private class EvenTask implements Runnable {

        @Override
        public void run() {
            reentrantLock.lock();
            try {
                while (number >= 0 && (number & 1) == 0) {
                    System.out.println("偶數(shù): " + (number--));
                    oddCondition.signal();
                    evenCondition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    }

    private class OddTask implements Runnable {
        @Override
        public void run() {
            reentrantLock.lock();
            try {
                while (number >= 0 && (number & 1) == 1) {
                    System.out.println("奇數(shù): " + (number--));
                    evenCondition.signal();
                    oddCondition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    }

運行后打有坡搿:

偶數(shù): 10
奇數(shù): 9
偶數(shù): 8
奇數(shù): 7
偶數(shù): 6
奇數(shù): 5
偶數(shù): 4
奇數(shù): 3
偶數(shù): 2
奇數(shù): 1
偶數(shù): 0

最后拯刁,建議使用 Lock/Condition 代替 Object 的 wait/notify,因為前者是 java.util.concurrent 包下的接口逝段,對于同步更簡潔高效垛玻,多線程操作優(yōu)先選用 JUC 包的類。

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奶躯,一起剝皮案震驚了整個濱河市帚桩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘹黔,老刑警劉巖账嚎,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異儡蔓,居然都是意外死亡郭蕉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門喂江,熙熙樓的掌柜王于貴愁眉苦臉地迎上來召锈,“玉大人,你說我怎么就攤上這事获询≌撬辏” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵吉嚣,是天一觀的道長梢薪。 經(jīng)常有香客問我,道長尝哆,這世上最難降的妖魔是什么秉撇? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上畜疾,老公的妹妹穿的比我還像新娘赴邻。我一直安慰自己,他們只是感情好啡捶,可當(dāng)我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布姥敛。 她就那樣靜靜地躺著,像睡著了一般瞎暑。 火紅的嫁衣襯著肌膚如雪彤敛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天了赌,我揣著相機與錄音墨榄,去河邊找鬼。 笑死勿她,一個胖子當(dāng)著我的面吹牛袄秩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逢并,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼之剧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了砍聊?” 一聲冷哼從身側(cè)響起背稼,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玻蝌,沒想到半個月后蟹肘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡俯树,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年帘腹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聘萨。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡竹椒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出米辐,到底是詐尸還是另有隱情,我是刑警寧澤书释,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布翘贮,位于F島的核電站,受9級特大地震影響爆惧,放射性物質(zhì)發(fā)生泄漏狸页。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芍耘。 院中可真熱鬧址遇,春花似錦、人聲如沸斋竞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坝初。三九已至浸剩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鳄袍,已是汗流浹背绢要。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拗小,地道東北人重罪。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像哀九,于是被迫代替她去往敵國和親剿配。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,665評論 2 354

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