亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

java 多線程-線程通信實例講解

 更新時間:2016年09月04日 08:48:41   作者:DemonWang  
本文主要介紹java 多線程-線程通信 這里整理了相關(guān)資料及示例代碼,有興趣的小伙伴可以參考下

線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號。另一方面,線程通信使線程能夠等待其他線程的信號。

  1. 通過共享對象通信
  2. 忙等待
  3. wait(),notify()和 notifyAll()
  4. 丟失的信號
  5. 假喚醒
  6. 多線程等待相同信號
  7. 不要對常量字符串或全局對象調(diào)用 wait()

通過共享對象通信

線程間發(fā)送信號的一個簡單方式是在共享對象的變量里設(shè)置信號值。線程 A 在一個同步塊里設(shè)置 boolean 型成員變量 hasDataToProcess 為 true,線程 B 也在同步塊里讀取 hasDataToProcess 這個成員變量。這個簡單的例子使用了一個持有信號的對象,并提供了 set 和 check 方法:

public class MySignal{

 protected boolean hasDataToProcess = false;

 public synchronized boolean hasDataToProcess(){
 return this.hasDataToProcess;
 }

 public synchronized void setHasDataToProcess(boolean hasData){
 this.hasDataToProcess = hasData;
 }

}

線程 A 和 B 必須獲得指向一個 MySignal 共享實例的引用,以便進行通信。如果它們持有的引用指向不同的 MySingal 實例,那么彼此將不能檢測到對方的信號。需要處理的數(shù)據(jù)可以存放在一個共享緩存區(qū)里,它和 MySignal 實例是分開存放的。

忙等待(Busy Wait)

準(zhǔn)備處理數(shù)據(jù)的線程 B 正在等待數(shù)據(jù)變?yōu)榭捎?。換句話說,它在等待線程 A 的一個信號,這個信號使 hasDataToProcess()返回 true。線程 B 運行在一個循環(huán)里,以等待這個信號:

protected MySignal sharedSignal = ...

...

while(!sharedSignal.hasDataToProcess()){
 //do nothing... busy waiting
}

wait(),notify()和 notifyAll()

忙等待沒有對運行等待線程的 CPU 進行有效的利用,除非平均等待時間非常短。否則,讓等待線程進入睡眠或者非運行狀態(tài)更為明智,直到它接收到它等待的信號。

Java 有一個內(nèi)建的等待機制來允許線程在等待信號的時候變?yōu)榉沁\行狀態(tài)。java.lang.Object 類定義了三個方法,wait()、notify()和 notifyAll()來實現(xiàn)這個等待機制。

一個線程一旦調(diào)用了任意對象的 wait()方法,就會變?yōu)榉沁\行狀態(tài),直到另一個線程調(diào)用了同一個對象的 notify()方法。為了調(diào)用 wait()或者 notify(),線程必須先獲得那個對象的鎖。也就是說,線程必須在同步塊里調(diào)用 wait()或者 notify()。以下是 MySingal 的修改版本——使用了 wait()和 notify()的 MyWaitNotify:

public class MonitorObject{
}

public class MyWaitNotify{

 MonitorObject myMonitorObject = new MonitorObject();

 public void doWait(){
 synchronized(myMonitorObject){
  try{
  myMonitorObject.wait();
  } catch(InterruptedException e){...}
 }
 }

 public void doNotify(){
 synchronized(myMonitorObject){
  myMonitorObject.notify();
 }
 }
}

等待線程將調(diào)用 doWait(),而喚醒線程將調(diào)用 doNotify()。當(dāng)一個線程調(diào)用一個對象的 notify()方法,正在等待該對象的所有線程中將有一個線程被喚醒并允許執(zhí)行(校注:這個將被喚醒的線程是隨機的,不可以指定喚醒哪個線程)。同時也提供了一個 notifyAll()方法來喚醒正在等待一個給定對象的所有線程。

如你所見,不管是等待線程還是喚醒線程都在同步塊里調(diào)用 wait()和 notify()。這是強制性的!一個線程如果沒有持有對象鎖,將不能調(diào)用 wait(),notify()或者 notifyAll()。否則,會拋出 IllegalMonitorStateException 異常。

(校注:JVM 是這么實現(xiàn)的,當(dāng)你調(diào)用 wait 時候它首先要檢查下當(dāng)前線程是否是鎖的擁有者,不是則拋出 IllegalMonitorStateExcept。)

但是,這怎么可能?等待線程在同步塊里面執(zhí)行的時候,不是一直持有監(jiān)視器對象(myMonitor 對象)的鎖嗎?等待線程不能阻塞喚醒線程進入 doNotify()的同步塊嗎?答案是:的確不能。一旦線程調(diào)用了 wait()方法,它就釋放了所持有的監(jiān)視器對象上的鎖。這將允許其他線程也可以調(diào)用 wait()或者 notify()。

一旦一個線程被喚醒,不能立刻就退出 wait()的方法調(diào)用,直到調(diào)用 notify()的

public class MyWaitNotify2{

 MonitorObject myMonitorObject = new MonitorObject();
 boolean wasSignalled = false;

 public void doWait(){
 synchronized(myMonitorObject){
  if(!wasSignalled){
  try{
   myMonitorObject.wait();
   } catch(InterruptedException e){...}
  }
  //clear signal and continue running.
  wasSignalled = false;
 }
 }

 public void doNotify(){
 synchronized(myMonitorObject){
  wasSignalled = true;
  myMonitorObject.notify();
 }
 }
}


線程退出了它自己的同步塊。換句話說:被喚醒的線程必須重新獲得監(jiān)視器對象的鎖,才可以退出 wait()的方法調(diào)用,因為 wait 方法調(diào)用運行在同步塊里面。如果多個線程被 notifyAll()喚醒,那么在同一時刻將只有一個線程可以退出 wait()方法,因為每個線程在退出 wait()前必須獲得監(jiān)視器對象的鎖。

丟失的信號(Missed Signals)

notify()和 notifyAll()方法不會保存調(diào)用它們的方法,因為當(dāng)這兩個方法被調(diào)用時,有可能沒有線程處于等待狀態(tài)。通知信號過后便丟棄了。因此,如果一個線程先于被通知線程調(diào)用 wait()前調(diào)用了 notify(),等待的線程將錯過這個信號。這可能是也可能不是個問題。不過,在某些情況下,這可能使等待線程永遠在等待,不再醒來,因為線程錯過了喚醒信號。

為了避免丟失信號,必須把它們保存在信號類里。在 MyWaitNotify 的例子中,通知信號應(yīng)被存儲在 MyWaitNotify 實例的一個成員變量里。以下是 MyWaitNotify 的修改版本:

public class MyWaitNotify2{

 MonitorObject myMonitorObject = new MonitorObject();
 boolean wasSignalled = false;

 public void doWait(){
 synchronized(myMonitorObject){
  if(!wasSignalled){
  try{
   myMonitorObject.wait();
   } catch(InterruptedException e){...}
  }
  //clear signal and continue running.
  wasSignalled = false;
 }
 }

 public void doNotify(){
 synchronized(myMonitorObject){
  wasSignalled = true;
  myMonitorObject.notify();
 }
 }
}

留意 doNotify()方法在調(diào)用 notify()前把 wasSignalled 變量設(shè)為 true。同時,留意 doWait()方法在調(diào)用 wait()前會檢查 wasSignalled 變量。事實上,如果沒有信號在前一次 doWait()調(diào)用和這次 doWait()調(diào)用之間的時間段里被接收到,它將只調(diào)用 wait()。

(校注:為了避免信號丟失, 用一個變量來保存是否被通知過。在 notify 前,設(shè)置自己已經(jīng)被通知過。在 wait 后,設(shè)置自己沒有被通知過,需要等待通知。)

假喚醒

由于莫名其妙的原因,線程有可能在沒有調(diào)用過 notify()和 notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)。無端端地醒過來了。

如果在 MyWaitNotify2 的 doWait()方法里發(fā)生了假喚醒,等待線程即使沒有收到正確的信號,也能夠執(zhí)行后續(xù)的操作。這可能導(dǎo)致你的應(yīng)用程序出現(xiàn)嚴重問題。

為了防止假喚醒,保存信號的成員變量將在一個 while 循環(huán)里接受檢查,而不是在 if 表達式里。這樣的一個 while 循環(huán)叫做自旋鎖(校注:這種做法要慎重,目前的 JVM 實現(xiàn)自旋會消耗 CPU,如果長時間不調(diào)用 doNotify 方法,doWait 方法會一直自旋,CPU 會消耗太大)。被喚醒的線程會自旋直到自旋鎖(while 循環(huán))里的條件變?yōu)?false。以下 MyWaitNotify2 的修改版本展示了這點:

public class MyWaitNotify3{

 MonitorObject myMonitorObject = new MonitorObject();
 boolean wasSignalled = false;

 public void doWait(){
 synchronized(myMonitorObject){
  while(!wasSignalled){
  try{
   myMonitorObject.wait();
   } catch(InterruptedException e){...}
  }
  //clear signal and continue running.
  wasSignalled = false;
 }
 }

 public void doNotify(){
 synchronized(myMonitorObject){
  wasSignalled = true;
  myMonitorObject.notify();
 }
 }
}

留意 wait()方法是在 while 循環(huán)里,而不在 if 表達式里。如果等待線程沒有收到信號就喚醒,wasSignalled 變量將變?yōu)?false,while 循環(huán)會再執(zhí)行一次,促使醒來的線程回到等待狀態(tài)。

多個線程等待相同信號

如果你有多個線程在等待,被 notifyAll()喚醒,但只有一個被允許繼續(xù)執(zhí)行,使用 while 循環(huán)也是個好方法。每次只有一個線程可以獲得監(jiān)視器對象鎖,意味著只有一個線程可以退出 wait()調(diào)用并清除 wasSignalled 標(biāo)志(設(shè)為 false)。一旦這個線程退出 doWait()的同步塊,其他線程退出 wait()調(diào)用,并在 while 循環(huán)里檢查 wasSignalled 變量值。但是,這個標(biāo)志已經(jīng)被第一個喚醒的線程清除了,所以其余醒來的線程將回到等待狀態(tài),直到下次信號到來。

不要在字符串常量或全局對象中調(diào)用 wait()

(校注:本章說的字符串常量指的是值為常量的變量)

本文早期的一個版本在 MyWaitNotify 例子里使用字符串常量(””)作為管程對象。以下是那個例子:

public class MyWaitNotify{

 String myMonitorObject = "";
 boolean wasSignalled = false;

 public void doWait(){
 synchronized(myMonitorObject){
  while(!wasSignalled){
  try{
   myMonitorObject.wait();
   } catch(InterruptedException e){...}
  }
  //clear signal and continue running.
  wasSignalled = false;
 }
 }

 public void doNotify(){
 synchronized(myMonitorObject){
  wasSignalled = true;
  myMonitorObject.notify();
 }
 }
}

在空字符串作為鎖的同步塊(或者其他常量字符串)里調(diào)用 wait()和 notify()產(chǎn)生的問題是,JVM/編譯器內(nèi)部會把常量字符串轉(zhuǎn)換成同一個對象。這意味著,即使你有 2 個不同的 MyWaitNotify 實例,它們都引用了相同的空字符串實例。同時也意味著存在這樣的風(fēng)險:在第一個 MyWaitNotify 實例上調(diào)用 doWait()的線程會被在第二個 MyWaitNotify 實例上調(diào)用 doNotify()的線程喚醒。這種情況可以畫成以下這張圖:

起初這可能不像個大問題。畢竟,如果 doNotify()在第二個 MyWaitNotify 實例上被調(diào)用,真正發(fā)生的事不外乎線程 A 和 B 被錯誤的喚醒了 。這個被喚醒的線程(A 或者 B)將在 while 循環(huán)里檢查信號值,然后回到等待狀態(tài),因為 doNotify()并沒有在第一個 MyWaitNotify 實例上調(diào)用,而這個正是它要等待的實例。這種情況相當(dāng)于引發(fā)了一次假喚醒。線程 A 或者 B 在信號值沒有更新的情況下喚醒。但是代碼處理了這種情況,所以線程回到了等待狀態(tài)。記住,即使 4 個線程在相同的共享字符串實例上調(diào)用 wait()和 notify(),doWait()和 doNotify()里的信號還會被 2 個 MyWaitNotify 實例分別保存。在 MyWaitNotify1 上的一次 doNotify()調(diào)用可能喚醒 MyWaitNotify2 的線程,但是信號值只會保存在 MyWaitNotify1 里。

問題在于,由于 doNotify()僅調(diào)用了 notify()而不是 notifyAll(),即使有 4 個線程在相同的字符串(空字符串)實例上等待,只能有一個線程被喚醒。所以,如果線程 A 或 B 被發(fā)給 C 或 D 的信號喚醒,它會檢查自己的信號值,看看有沒有信號被接收到,然后回到等待狀態(tài)。而 C 和 D 都沒被喚醒來檢查它們實際上接收到的信號值,這樣信號便丟失了。這種情況相當(dāng)于前面所說的丟失信號的問題。C 和 D 被發(fā)送過信號,只是都不能對信號作出回應(yīng)。

如果 doNotify()方法調(diào)用 notifyAll(),而非 notify(),所有等待線程都會被喚醒并依次檢查信號值。線程 A 和 B 將回到等待狀態(tài),但是 C 或 D 只有一個線程注意到信號,并退出 doWait()方法調(diào)用。C 或 D 中的另一個將回到等待狀態(tài),因為獲得信號的線程在退出 doWait()的過程中清除了信號值(置為 false)。

看過上面這段后,你可能會設(shè)法使用 notifyAll()來代替 notify(),但是這在性能上是個壞主意。在只有一個線程能對信號進行響應(yīng)的情況下,沒有理由每次都去喚醒所有線程。

所以:在 wait()/notify()機制中,不要使用全局對象,字符串常量等。應(yīng)該使用對應(yīng)唯一的對象。例如,每一個 MyWaitNotify3 的實例擁有一個屬于自己的監(jiān)視器對象,而不是在空字符串上調(diào)用 wait()/notify()。

以上就是關(guān)于Java 多線程,線程通信的資料整理,后續(xù)繼續(xù)補充相關(guān)資料,謝謝大家對本站的支持!

相關(guān)文章

  • intellij idea如何將web項目打成war包的實現(xiàn)

    intellij idea如何將web項目打成war包的實現(xiàn)

    這篇文章主要介紹了intellij idea如何將web項目打成war包的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Java 進程執(zhí)行外部程序造成阻塞的一種原因

    Java 進程執(zhí)行外部程序造成阻塞的一種原因

    前一陣子在研究文檔展示時使用了java進程直接調(diào)用外部程序,其中遇到一個問題花了好長時間才解決,這個問題就是外部程序直接執(zhí)行沒什么問題,但是當(dāng)使用Java進程執(zhí)行時外部程序就阻塞在那兒不動了。而且這個外部程序在處理某些文件時使用Java進程執(zhí)行是沒問題的
    2014-03-03
  • Java注釋代碼執(zhí)行方法解析

    Java注釋代碼執(zhí)行方法解析

    這篇文章主要介紹了Java注釋代碼執(zhí)行方法解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • Mybatis自定義類型轉(zhuǎn)換器的使用技巧

    Mybatis自定義類型轉(zhuǎn)換器的使用技巧

    這篇文章主要介紹了Mybatis自定義類型轉(zhuǎn)換器的使用技巧,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java List 集合如何去除null元素

    Java List 集合如何去除null元素

    這篇文章主要介紹了Java List 集合如何去除null元素,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java 在PPT中添加混合圖表過程詳解

    Java 在PPT中添加混合圖表過程詳解

    這篇文章主要介紹了Java 在PPT中添加混合圖表過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • 詳解Spring Aop實例之AspectJ注解配置

    詳解Spring Aop實例之AspectJ注解配置

    本篇文章主要介紹了詳解Spring Aop實例之AspectJ注解配置,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • Java線程中斷的本質(zhì)深入理解

    Java線程中斷的本質(zhì)深入理解

    Java的中斷是一種協(xié)作機制。也就是說調(diào)用線程對象的interrupt方法并不一定就中斷了正在運行的線程,它只是要求線程自己在合適的時機中斷自己,本文將詳細介紹,需要了解的朋友可以參考下
    2012-12-12
  • jsp頁面中獲取servlet請求中的參數(shù)的辦法詳解

    jsp頁面中獲取servlet請求中的參數(shù)的辦法詳解

    在JAVA WEB應(yīng)用中,如何獲取servlet請求中的參數(shù),本文講解了jsp頁面中獲取servlet請求中的參數(shù)的辦法
    2018-03-03
  • Java使用正則表達式驗證手機號和電話號碼的方法

    Java使用正則表達式驗證手機號和電話號碼的方法

    今天小編就為大家分享一篇關(guān)于Java使用正則表達式驗證手機號和電話號碼的方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-12-12

最新評論