您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Java中線程通信及線程虛假喚醒的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
線程在內部運行時,線程調度具有一定的透明性,程序通常無法控制線程的輪換執行。但Java本身提供了一些機制來保證線程協調運行。
假設目前系統中有兩個線程,分別代表存款和取錢。當錢存進去,立馬就取出來挪入指定賬戶。這涉及到線程間的協作,使用到Object類提供的wait()、notify()、notifyAll()三個方法,其不屬于Thread類,而屬于Object,而這三個方法必須由監視器對象來調用:
synchronized修飾的方法,因為該類的默認實例(this)就是同步監視器,因此可以在同步方法中直接調用
synchronized修飾的同步代碼塊,同步監視器是synchronized括號里的對象,因此必須使用該對象來調用
三個方法解釋如下:
wait():當前線程等待,釋放當前對象鎖,讓出CPU,直到其他線程使用notify或者notifyAll喚醒該線程
notify():喚醒在此同步監視器上等待的單個線程,若存在多個線程,則隨機喚醒一個。執行了notify不會馬上釋放鎖,只有完全退出synchronized代碼塊或者中途遇到wait,呈wait狀態的線程才可以去爭取該對象鎖
notifyAll():喚醒在此同步監視器上的所有線程,同上。
現在用兩個同步方法分別代表存錢取錢
當余額為0時,進入存錢流程,執行存錢操作后,喚醒取錢線程
當余額為0時,進入取錢流程,發現num==0,進入阻塞狀態,等待被喚醒
/** * 存一塊錢 * * @throws InterruptedException */ public synchronized void increase() throws InterruptedException { // 當余額為1,說明已經存過錢,等待取錢。存錢方法阻塞 if (num == 1) { this.wait(); } // 執行存錢操作 num++; System.out.println(Thread.currentThread().getName() + ":num=" + num); // 喚醒其他線程 this.notifyAll(); } /** * 取一塊錢 * * @throws InterruptedException */ public synchronized void decrease() throws InterruptedException { // 當余額為0,說明已經取過錢,等待存錢。取錢方法阻塞 if (num == 0) { this.wait(); } num--; System.out.println(Thread.currentThread().getName() + ":num=" + num); this.notifyAll(); }
調用方法:
private int num = 0; public static void main(String[] args) { Test test = new Test(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { test.increase(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "存錢").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { test.decrease(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "取錢").start(); }
結果沒有什么問題
上述線程通信看起來似乎沒有什么問題,但若此時將存錢和取錢的人數各增加1,再看運行結果
private int num = 0; public static void main(String[] args) { Test test = new Test(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { test.increase(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "存錢1").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { test.decrease(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "取錢1").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { test.increase(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "存錢2").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { test.decrease(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "取錢2").start(); }
產生的結果已經不是最初的只有0和1
造成這個結果的原因就是線程間的虛假喚醒
由于目前分別有多個取款和存款線程。假設其中一個存款線程執行完畢,并使用wait釋放同步監視器鎖定,那其余多個取款線程將同時被喚醒,此時余額為1,如果有10個同時取錢,那余額會變為-9,造成結果錯誤。
因此,每次線程從wait中被喚醒,都必須再次測試是否符合喚醒條件,如果不符合那就繼續等待。
由于多個線程被同時喚醒,在if(xxxx){wait();}處 if判斷只會執行一次,當下一個被喚醒的線程過來時,由于if已經判斷過,則直接從wait后面的語句繼續執行,因此將if換成while可解決該問題,下次被喚醒的線程過來,while重新判斷一下,發現上一個被喚醒的線程已經拿到鎖,因此這個被虛假喚醒的線程將繼續等待鎖。
/** * 存一塊錢 * * @throws InterruptedException */ public synchronized void increase() throws InterruptedException { while (num == 1) {// 防止每次進來的喚醒線程只判斷一次造成虛假喚醒,替換成while this.wait(); } num++; System.out.println(Thread.currentThread().getName() + ":num=" + num); this.notifyAll(); } /** * 取一塊錢 * * @throws InterruptedException */ public synchronized void decrease() throws InterruptedException { while (num == 0) { this.wait(); } num--; System.out.println(Thread.currentThread().getName() + ":num=" + num); this.notifyAll(); }
再次運行,結果正常:
關于“Java中線程通信及線程虛假喚醒的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。