成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

JAVA線程間通信簡介

CHENGKANG / 1683人閱讀

摘要:線程通信的目標(biāo)是使線程間能夠互相發(fā)送信號。但是,這個(gè)標(biāo)志已經(jīng)被第一個(gè)喚醒的線程清除了,所以其余醒來的線程將回到等待狀態(tài),直到下次信號到來。如果方法調(diào)用,而非,所有等待線程都會被喚醒并依次檢查信號值。

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

例如,線程B可以等待線程A的一個(gè)信號,這個(gè)信號會通知線程B數(shù)據(jù)已經(jīng)準(zhǔn)備好了。本文將講解以下幾個(gè)JAVA線程間通信的主題:

1、通過共享對象通信

2、忙等待

3、wait(),notify()和notifyAll()

4、丟失的信號

5、假喚醒

6、多線程等待相同信號

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

1、通過共享對象通信

線程間發(fā)送信號的一個(gè)簡單方式是在共享對象的變量里設(shè)置信號值。線程A在一個(gè)同步塊里設(shè)置boolean型成員變量hasDataToProcess為true,線程B也在同步塊里讀取hasDataToProcess這個(gè)成員變量。這個(gè)簡單的例子使用了一個(gè)持有信號的對象,并提供了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必須獲得指向一個(gè)MySignal共享實(shí)例的引用,以便進(jìn)行通信。如果它們持有的引用指向不同的MySingal實(shí)例,那么彼此將不能檢測到對方的信號。需要處理的數(shù)據(jù)可以存放在一個(gè)共享緩存區(qū)里,它和MySignal實(shí)例是分開存放的。

2、忙等待(Busy Wait)

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

protected MySignal sharedSignal = ...

...

while(!sharedSignal.hasDataToProcess()){
  //do nothing... busy waiting
}
3、wait(),notify()和notifyAll()

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

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

一個(gè)線程一旦調(diào)用了任意對象的wait()方法,就會變?yōu)榉沁\(yùn)行狀態(tài),直到另一個(gè)線程調(diào)用了同一個(gè)對象的notify()方法。為了調(diào)用wait()或者notify(),線程必須先獲得那個(gè)對象的鎖。也就是說,線程必須在同步塊里調(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)一個(gè)線程調(diào)用一個(gè)對象的notify()方法,正在等待該對象的所有線程中將有一個(gè)線程被喚醒并允許執(zhí)行(校注:這個(gè)將被喚醒的線程是隨機(jī)的,不可以指定喚醒哪個(gè)線程)。同時(shí)也提供了一個(gè)notifyAll()方法來喚醒正在等待一個(gè)給定對象的所有線程。

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

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

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

一旦一個(gè)線程被喚醒,不能立刻就退出wait()的方法調(diào)用,直到調(diào)用notify()的線程退出了它自己的同步塊。換句話說:被喚醒的線程必須重新獲得監(jiān)視器對象的鎖,才可以退出wait()的方法調(diào)用,因?yàn)閣ait方法調(diào)用運(yùn)行在同步塊里面。如果多個(gè)線程被notifyAll()喚醒,那么在同一時(shí)刻將只有一個(gè)線程可以退出wait()方法,因?yàn)槊總€(gè)線程在退出wait()前必須獲得監(jiān)視器對象的鎖。

4、丟失的信號(Missed Signals)

notify()和notifyAll()方法不會保存調(diào)用它們的方法,因?yàn)楫?dāng)這兩個(gè)方法被調(diào)用時(shí),有可能沒有線程處于等待狀態(tài)。通知信號過后便丟棄了。因此,如果一個(gè)線程先于被通知線程調(diào)用wait()前調(diào)用了notify(),等待的線程將錯過這個(gè)信號。這可能是也可能不是個(gè)問題。不過,在某些情況下,這可能使等待線程永遠(yuǎn)在等待,不再醒來,因?yàn)榫€程錯過了喚醒信號。
為了避免丟失信號,必須把它們保存在信號類里。在MyWaitNotify的例子中,通知信號應(yīng)被存儲在MyWaitNotify實(shí)例的一個(gè)成員變量里。以下是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。同時(shí),留意doWait()方法在調(diào)用wait()前會檢查wasSignalled變量。事實(shí)上,如果沒有信號在前一次doWait()調(diào)用和這次doWait()調(diào)用之間的時(shí)間段里被接收到,它將只調(diào)用wait()。

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

5、假喚醒

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

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

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

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表達(dá)式里。如果等待線程沒有收到信號就喚醒,wasSignalled變量將變?yōu)閒alse,while循環(huán)會再執(zhí)行一次,促使醒來的線程回到等待狀態(tài)。

6、多個(gè)線程等待相同信號

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

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

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

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

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)換成同一個(gè)對象。這意味著,即使你有2個(gè)不同的MyWaitNotify實(shí)例,它們都引用了相同的空字符串實(shí)例。同時(shí)也意味著存在這樣的風(fēng)險(xiǎn):在第一個(gè)MyWaitNotify實(shí)例上調(diào)用doWait()的線程會被在第二個(gè)MyWaitNotify實(shí)例上調(diào)用doNotify()的線程喚醒。這種情況可以畫成以下這張圖:

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

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

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

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

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

校注:

<

p>管程 (英語:Monitors,也稱為監(jiān)視器) 是對多個(gè)工作線程實(shí)現(xiàn)互斥訪問共享資源的對象或模塊。這些共享資源一般是硬件設(shè)備或一群變量。管程實(shí)現(xiàn)了在一個(gè)時(shí)間點(diǎn),最多只有一個(gè)線程在執(zhí)行它的某個(gè)子程序。與那些通過修改數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)互斥訪問的并發(fā)程序設(shè)計(jì)相比,管程很大程度上簡化了程序設(shè)計(jì)。

<

p>

原文 Thread Signaling
譯者:杜建雄 校對:方騰飛
via ifeve

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/69759.html

相關(guān)文章

  • Java并發(fā)編程之線程通訊(下)-生產(chǎn)者與消費(fèi)者

    摘要:前文回顧上一篇文章重點(diǎn)嘮叨了中協(xié)調(diào)線程間通信的機(jī)制,它有力的保證了線程間通信的安全性以及便利性。所以同一時(shí)刻廚師線程和服務(wù)員線程不會同時(shí)在等待隊(duì)列中。對于在操作系統(tǒng)中線程的阻塞狀態(tài),語言中用和這三個(gè)狀態(tài)分別表示。 前文回顧 上一篇文章重點(diǎn)嘮叨了java中協(xié)調(diào)線程間通信的wait/notify機(jī)制,它有力的保證了線程間通信的安全性以及便利性。本篇將介紹wait/notify機(jī)制的一個(gè)應(yīng)用...

    lufficc 評論0 收藏0
  • 如何使用 volatile, synchronized, final 進(jìn)行線程通信

    摘要:如線程執(zhí)行后,線程執(zhí)行,相當(dāng)于線程向線程發(fā)送了消息。我們可以利用這種互斥性來進(jìn)行線程間通信。 你是否真正理解并會用volatile, synchronized, final進(jìn)行線程間通信呢,如果你不能回答下面的幾個(gè)問題,那就說明你并沒有真正的理解: 對volatile變量的操作一定具有原子性嗎? synchronized所謂的加鎖,鎖住的是什么? final定義的變量不變的到底是什么...

    keithxiaoy 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<