Java并發編程(五)---線程通信

      網友投稿 646 2025-04-02

      文章首發于:https://mp.weixin.qq.com/s/sbkY-Il1AQ0Ew-b7LKc33g

      前言

      上一篇我們介紹了死鎖的發生條件,以及避免死鎖的方式。其中 破壞占有且等待的處理是,通過一個單例類一次性申請所有資源,直到成功。如while (!Allocator.getAllocator().applyResource(this, target)) { return; } 如果在并發量比較小的情況下,還可以接受,如果并發量比較大的話,就會大量的消耗CPU的資源。這時候,我們應該引入線程通信,主要是 等待-喚醒機制。

      線程通信

      下面我們通過一個例子來說明。場景說明:

      圖書館里,有一本書叫《Java 高并發實戰》,小A早上的時候把這本書借走了,小B中午的時候去圖書館找這本書,這里小A和小B分別是兩個線程,他們都要看的書是共享資源。

      通過共享對象通信

      小B去了圖書館,發現這本書被借走了,他回到家,等了幾天,再去圖書館找這本書,發現這本書已經被歸還了,他順利借走了書。

      用程序模擬的話,就是線程小A調用setCanBorrow方法將canBorrow設為true,線程小B調用getCanBorrow獲取。在這里線程A和B必須獲得

      指向同一個LibraryTest1共享實例的引用,如果持有的對象指向不同的LibraryTest1實例,那么彼此將不能檢測到對方的信號。

      public class LibraryTest1 { //是否可借 private boolean canBorrow = false; synchronized boolean getCanBorrow() { return canBorrow; } synchronized void setCanBorrow(boolean canBorrow) { this.canBorrow = canBorrow; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      忙等待

      其實小A在小B走后一會就把書還回去了,小B卻在幾天后才去找書,為了早點借到書(減少延遲),小B可能在圖書館等著,每隔幾分鐘(while循環),他就去檢查這本書有沒有被還回,這樣只要小A一還回書,小B馬上就會知道。

      public class LibraryTest1 { final LibraryTest1 libraryTest1 = new LibraryTest1(); while (!libraryTest1.getCanBorrow()) { //空等 return; } }

      1

      2

      3

      4

      5

      6

      7

      wait(),notify()和notifyAll()

      很多次后,小B發現自己這樣做太累了,身體有點吃不消,不過很快,學校圖書館系統改進,加入了短信通知功能(notify()),只要小A一還回書,圖書館會立馬通知小B,這樣小B就可以在家睡覺等短信了。

      //是否可借 private boolean canBorrow = false; synchronized String borrowBook() throws InterruptedException { if (!canBorrow) { wait(); return null; } canBorrow = false; return "Java 高并發實戰"; } synchronized void giveBackBook() { this.canBorrow = true; notify(); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      如上程序,canBorrow 初始為false時(書本已經被小A借走),小A 還書(調用giveBackBook方法)之后,書的可借狀態為可借,并且調用notify()通知等待線程(系統發短信給小B)需要注意的兩點是:

      wait(),notify() 和或者notifyAll() 都需要在同步代碼塊中調用(就是消息只能由圖書管理系統發出),不能再同步代碼塊之外調用,否則,會拋出IllegalMonitorStateException異常。

      notify() 是隨機的通知等待隊列里的某一個線程,而notifyAll()是通知等待隊列里的所有線程。一般而言最好調用notifyAll(),而不要調用notify()。因為,通知時,只是表示通知時間點條件滿足,等線程執行時,條件可能已經不滿足了,線程的執行時間與通知時間不重合,如果調用notify()的話很快能不能通知到我們期望通知的線程。

      相同點:都會讓當前線程掛起一段時間,讓渡CPU的執行時間,等待再次調度

      不同點:

      wait(),notify(),notifyAll()一定是在synchronized{}內部調用,等待和通知的對象必須要對應。而sleep可以再任何地方調用

      wait 會釋放鎖對象的“鎖標志”,當調用某一對象的wait()方法后,會使當前線程暫停執行,并將當前線程放入對象等待池中。知道調用notifyAll()方法,而sleep則不會釋放,也就是說在休眠期間,其他線程仍然不能訪問共享數據。

      wait可以被喚醒,sleep的只能等其睡眠結束

      wait()是在Object 類里,而sleep是在Thread 里的

      丟失的信號

      學校圖書館系統是這么設計的,當一本書被還回來的時候,會給等待著發送短信,并且只會發一次,如果沒有等待者,他也會發(只不過沒有接受者)。問題出現了,因為短信只會發一次,當書被還回來的時候,沒有人等待借書,他會發一條空短信,但是之后有等待借此本書的同學永遠也不會再收到短信,導致這些同學會無休止的等待,為了避免這個問題,我們等待的時候先打個電話問問圖書管理員是否繼續等待if(!wasSignalled)。

      public class LibraryTest3 { private MonitorObject monitorObject = new MonitorObject(); private boolean canBorrow = false; private boolean wasSignalled = false; String borrowBook() throws InterruptedException { synchronized (monitorObject) { if (!canBorrow||!wasSignalled) { wait(); return null; } canBorrow = false; return "Java 高并發實戰"; } } void giveBackBook() { synchronized (monitorObject) { this.canBorrow = true; this.wasSignalled = true; notifyAll(); } } }

      1

      2

      3

      4

      5

      6

      Java并發編程(五)---線程通信

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      notify()和notifyAll()方法不會保存調用它們的方法,因為當這兩個方法被調用時,有可能沒有線程處于等待狀態,通知信號過后便丟棄了。因此,如果一個線程先于被通知線程調用wait()前調用notify(),等待的線程就將錯過這個信號。在某些情況下,這可能使得等待線程永久等待,不再被喚醒。所以,為了避免丟失信號,我們需要將信號保存到信號類里。這里的管理員就是信號類。

      假喚醒

      圖書館系統還有一個BUG:系統會偶爾給你發條錯誤短信,說書可以借了(其實不可以借),我們之前已經該圖書館管理員打過電話了,他說讓我們等短信。我們很聽話,一等到短信(其實是bug引起的錯誤短信),就去借書了,到了圖書館后發現這書根本沒有還回來!我們很郁悶,但也沒有辦法呀,學校不修復BUG,我們得聰明點:每次在收到短信后,再打電話問問書到底能不能借while(!canBorrow||!wasSignalled)。

      String borrowBook() throws InterruptedException { synchronized (monitorObject) { while (!canBorrow||!wasSignalled) { wait(); return null; } canBorrow = false; return "Java 高并發實戰"; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      就像案例里說的,有時候線程會被莫名奇妙的喚醒,為了防止假喚醒,保存信號的成員變量將在一個while循環里接收檢查。而不是在if表達式里。

      不要對常量字符串或全局對象調用wait()

      因為如果使用常量字符串的話。JVM/編譯器內部會把常量字符串轉成同一個對象,

      這意味著即使你有2個不同的MyWaitNotify3實例,它們都是引用了相同的空字符串實例。同時也意味著存在這樣的風險,在第一個MyWaitNotify3實例調用doWait()會被第二個MyWaitNotify3實例上調用doNotify()的線程喚醒。

      public class MyWaitNotify3 { String monitorObject = ""; boolean isSignaled = false; public void doWait() { synchronized (monitorObject) { while (!isSignaled) { try { monitorObject.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //清楚標記,繼續執行 isSignaled = false; } } public void doNotify() { synchronized (monitorObject) { isSignaled = true; monitorObject.notifyAll(); } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      總結

      本文主要介紹了線程之間的通信,通過一個現實中的場景再現了這5種場景,其中等待-通知機制是線程通信的核心機制。工作中用輪詢的方式來等待某個狀態,很多情況下都可以用等待-通知機制。

      參考資料:

      線程通信

      Java 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:企業協作平臺——加強團隊合作,提升工作效率
      下一篇:【圖神經網絡DGL】消息傳遞范式(圖神經網絡 消息傳遞)
      相關文章
      99久久国产亚洲综合精品| 亚洲精品91在线| 亚洲人成电影院在线观看| 亚洲色图在线播放| 亚洲av无码一区二区三区网站| 亚洲愉拍99热成人精品热久久| 久久久亚洲精品蜜桃臀| 2022中文字字幕久亚洲| 国产精品亚洲高清一区二区| 亚洲AV无码一区二三区| 亚洲av无码天堂一区二区三区 | 国产精品亚洲va在线观看| 亚洲欧美第一成人网站7777| 亚洲熟妇无码八V在线播放 | 亚洲日本一区二区三区在线| 伊人久久亚洲综合| 亚洲无人区午夜福利码高清完整版| 国产亚洲精品无码专区 | 亚洲中文精品久久久久久不卡| 亚洲经典千人经典日产| 久久久久亚洲国产AV麻豆| 朝桐光亚洲专区在线中文字幕 | 亚洲av午夜精品一区二区三区| 亚洲精品成人久久久| 久久伊人亚洲AV无码网站| 亚洲区小说区激情区图片区| 亚洲国产精品一区二区第一页| 婷婷亚洲久悠悠色悠在线播放 | jizzjizz亚洲| 中文字幕亚洲乱码熟女一区二区 | 亚洲国产精品无码久久久蜜芽 | 久久久亚洲欧洲日产国码二区 | 亚洲三级视频在线| 亚洲偷自拍另类图片二区| 亚洲成人国产精品| 亚洲精品夜夜夜妓女网| 亚洲免费在线播放| 亚洲三级在线视频| 狼人大香伊蕉国产WWW亚洲| 久久精品夜色噜噜亚洲A∨| 亚洲AV无码国产在丝袜线观看 |