您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java線程通信中wait-notify通信的方式是什么”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Java線程通信中wait-notify通信的方式是什么”文章能幫助大家解決問題。
線程是操作系統調度的最小單位,有自己的棧空間,可以按照既定的代碼逐步執行,但是如果每個線程間都孤立地運行,就會造資源浪費。所以在現實中,如果需要多個線程按照指定的規則共同完成一個任務,那么這些線程之間就需要互相協調,這個過程被稱為線程的通信。
線程的通信可以被定義為:當多個線程共同操作共享的資源時,線程間通過某種方式互相告知自己的狀態,以避免無效的資源爭奪。
線程間通信的方式可以有很多種:等待-通知、共享內存、管道流。“等待-通知”通信方式是Java中使用普遍的線程間通信方式,其經典的案例是“生產者-消費者”模式。
場景:有幾個小孩都想進入房間內使用算盤(CPU)進行計算,老王(操作系統)就使用了一把鎖(synchronized)讓同一時間只有一個小孩能進入房間使用算盤,于是他們排隊進入房間。
(1) 小南最先獲取到了鎖,進入到房間內,但是由于條件不滿足(沒煙干不了活),小南不能繼續進行計算 ,但小南如果一直占用著鎖,其它人就得一直阻塞,效率太低。
(2) 于是老王單開了一間休息室(調用 wait 方法),讓小南到休息室(WaitSet)等著去了,這時鎖釋放開, 其它人可以由老王隨機安排進屋
(3) 直到小M將煙送來,大叫一聲 [ 你的煙到了 ] (調用 notify 方法)
(4) 小南于是可以離開休息室,重新進入競爭鎖的隊列
java語言中“等待-通知”方式的線程間通信使用對象的wait()、notify()
兩類方法來實現。每個java對象都有wait()、notify(
)兩類實例方法,并且wait()、notify()
方法和對象的監視器是緊密相關的。
wait()、notify()
兩類方法在數量上不止兩個。wait()、notify()
兩類方法不屬于Thread類,而是屬于java對象實例。
java
對象中的wait()、notify()
兩類方法就如同信號開關,用于等待方和通知方之間的交互。
對象的wait()
方法的主要作用是讓當前線程阻塞并等待被喚醒。wait()
方法與對象監視器緊密相關,使用wait()
方法時一定要放在同步塊中。wait()方法的調用方法如下:
public class Main { static final Object lock = new Object(); public static void method1() throws InterruptedException { synchronized( lock ) { lock.wait(); } } }
Object類中的wait()方法有三個版本:
(1) void wait()
:當前線程調用了同步對象lock
的wait()
實例方法后,將導致當前的線程等待,當前線程進入lock的監視器WaitSet,等待被其他線程喚醒;
(2) void wait(long timeout)
:限時等待。導致當前的線程等待,等待被其他線程喚醒,或者指定的時間timeout用完,線程不再等待;
(3) void wait(long timeout,int nanos)
:高精度限時等待,其主要作用是更精確地控制等待時間。參數nanos是一個附加的納秒級別的等待時間;
對象的notify()
方法的主要作用是喚醒在等待的線程。notify()方法與對象監視器緊密相關,調用notify()方法時也需要放在同步塊中。notify()方法的調用方法如下:
public class Main { static final Object lock = new Object(); public static void method1() throws InterruptedException { synchronized( lock ) { lock.notify(); } } }
notify()方法有兩個版本:
(1)void notify()
:lock.notify()
調用后,喚醒lock監視器等待集中的第一條等待線程;被喚醒的線程進入EntryList,其狀態從WAITING變成BLOCKED。
(2) void notifyAll()
:lock.notifyAll()
被調用后,喚醒lock監視器等待集中的全部等待線程,所有被喚醒的線程進入EntryList,線程狀態從WAITING變成BLOCKED。
小結:
obj.wait()
:讓進入Object監視器的線程到waitset等待
obj.notify()
:在Object上正在waitset等待的線程中挑一個喚醒
obj.notifyAll()
:讓在Object上正在waitset等待的線程全部喚醒
對象的wait()方法的核心原理大致如下:
(1) 當線程調用了lock
(某個同步鎖對象)的wait()
方法后,jvm會將當前線程加入lock
監視器的WaitSet(等待集),等待被其他線程喚醒。
(2) 當前線程會釋放lock
對象監視器的Owner權利,讓其他線程可以搶奪lock
對象的監視器。
(3) 讓當前線程等待,其狀態變成WAITING。在線程調用了同步對象lock的wait()
方法之后,同步對象lock
的監視器內部狀態大致如圖2-15所示。
對象的notify()或者notifyAll()方法的原理大致如下:
(1) 當線程調用了lock
(某個同步鎖對象)的notify()
方法后,jvm會喚醒lock
監視器WaitSet中的第一條等待線程。
(2) 當線程調用了lock
的notifyAll()
方法后,jvm會喚醒lock監視器WaitSet中的所有等待線程。
(3) 等待線程被喚醒后,會從監視器的WaitSet移動到EntryList,線程具備了排隊搶奪監視器Owner權利的資格,其狀態從WAITING變成BLOCKED。
(4) EntryList中的線程搶奪到監視器的Owner權利之后,線程的狀態從BLOCKED變成Runnable,具備重新執行的資格。
(1) Owner 線程發現條件不滿足,調用wait
方法,即可進入WaitSet,變為 WAITING 狀態 ;
(2) BLOCKED 和WAITING 的線程都處于阻塞狀態,不占用CPU時間片 ;
(3) BLOCKED:線程會在Owner 線程釋放鎖時喚醒 ;
(4) WAITING :線程會在Owner 線程調用notify
或 notifyAll
時喚醒,但喚醒后并不意味者立刻獲得鎖,仍需進入 EntryList 重新競爭;
小南并不能直接進入WaitSet休息室,而是獲取鎖進入房間后才能進入休息室,沒有鎖的話小南沒法進入房間,更沒法進入休息室。他進入休息室后就會釋放鎖,讓其他線程競爭鎖進入房間。
public class Main { static final Object lock = new Object(); public static void main(String[] args) { synchronized (lock){ try { // 只有成為了monitor對象的owner,即獲得了對象鎖之后,才有資格進入waitset等待 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
小M此時獲取到了鎖,進入了房間,并喚醒了在休息室中等待的小王,小M如果獲取到鎖進行房間時沒有辦法喚醒在休息室等待的小王的,因為此時小M在門外,小王根本聽不到。
使用notify()喚醒等待區的一個線程:
public class Main { static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ System.out.println("t1線程開始執行..."); synchronized (lock){ try { // 讓t1線程在lock鎖的waitset中等待 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 被喚醒后,繼續執行 System.out.println("線程t1被喚醒..."); } },"t1"); t1.start(); Thread t2 = new Thread(()->{ System.out.println("t2線程開始執行..."); synchronized (lock){ try { // 讓t2線程在lock鎖的waitset中等待 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 被喚醒后,繼續執行 System.out.println("線程t2被喚醒..."); },"t2"); t2.start(); Thread.sleep(2000); System.out.println("喚醒lock鎖上等待的線程..."); synchronized (lock){ // 主線程拿到鎖后,喚醒正在休息室中等待的某一個線程 lock.notify(); } } }
執行結果:
t1線程開始執行...
t2線程開始執行...
喚醒lock鎖上等待的線程...
線程t1被喚醒...
使用notifyAll()喚醒等待區所有的線程:
public class Main { static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ System.out.println("t1線程開始執行..."); synchronized (lock){ try { // 讓t1線程在lock鎖的waitset中等待 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } // 被喚醒后,繼續執行 System.out.println("線程t1被喚醒..."); } },"t1"); t1.start(); Thread t2 = new Thread(()->{ System.out.println("t2線程開始執行..."); synchronized (lock){ try { // 讓t2線程在lock鎖的waitset中等待 lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 被喚醒后,繼續執行 System.out.println("線程t2被喚醒..."); },"t2"); t2.start(); Thread.sleep(2000); System.out.println("喚醒lock鎖上等待的線程..."); synchronized (lock){ // 主線程拿到鎖后,喚醒正在休息室中等待的所有線程 lock.notifyAll(); } } }
執行結果:
t1線程開始執行...
t2線程開始執行...
喚醒lock鎖上等待的線程...
線程t2被喚醒...
線程t1被喚醒...
在調用同步對象的wait()和notify()系列方法時,“當前線程”必須擁有該對象的同步鎖,也就是說,wait()和notify()系列方法需要在同步塊中使用,否則JVM會拋出類似如下的異常:
為什么wait和notify不在synchronized同步塊的內部使用會拋出異常呢?這需要從wait()和notify()方法的原理說起。
wait()方法的原理:
首先,JVM會釋放當前線程的對象鎖監視器的Owner資格;其次,JVM會將當前線程移入監視器的WaitSet隊列,而這些操作都和對象鎖監視器是相關的。所以,wait()方法必須在synchronized同步塊的內部調用。在當前線程執行wait()方法前,必須通過synchronized()方法成為對象鎖的監視器的Owner。
notify()方法的原理:
JVM從對象鎖的監視器的WaitSet隊列移動一個線程到其EntryList隊列,這些操作都與對象鎖的監視器有關。所以,notify()方法也必須在synchronized同步塊的內部調用。在執行notify()方法前,當前線程也必須通過synchronized()方法成為對象鎖的監視器的Owner。
關于“Java線程通信中wait-notify通信的方式是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。