程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java 多線程-線程通訊實例講授

java 多線程-線程通訊實例講授

編輯:關於JAVA

java 多線程-線程通訊實例講授。本站提示廣大學習愛好者:(java 多線程-線程通訊實例講授)文章只能為提供參考,不一定能成為您想要的結果。以下是java 多線程-線程通訊實例講授正文


線程通訊的目的是使線程間可以或許相互發送旌旗燈號。另外一方面,線程通訊使線程可以或許期待其他線程的旌旗燈號。

  1. 經由過程同享對象通訊
  2. 忙期待
  3. wait(),notify()和 notifyAll()
  4. 喪失的旌旗燈號
  5. 假叫醒
  6. 多線程期待雷同旌旗燈號
  7. 不要對常量字符串或全局對象挪用 wait()
  8. 經由過程同享對象通訊

    線程間發送旌旗燈號的一個簡略方法是在同享對象的變量裡設相信號值。線程 A 在一個同步塊裡設置 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 實例,那末彼此將不克不及檢測到對方的旌旗燈號。須要處置的數據可以寄存在一個同享緩存區裡,它和 MySignal 實例是離開寄存的。

    忙期待(Busy Wait)

    預備處置數據的線程 B 正在期待數據變成可用。換句話說,它在期待線程 A 的一個旌旗燈號,這個旌旗燈號使 hasDataToProcess()前往 true。線程 B 運轉在一個輪回裡,以期待這個旌旗燈號:

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

    wait(),notify()和 notifyAll()

    忙期待沒有對運轉期待線程的 CPU 停止有用的應用,除非均勻期待時光異常短。不然,讓期待線程進入眠眠或許非運轉狀況更加明智,直到它吸收到它期待的旌旗燈號。

    Java 有一個內建的期待機制來許可線程在期待旌旗燈號的時刻變成非運轉狀況。java.lang.Object 類界說了三個辦法,wait()、notify()和 notifyAll()來完成這個期待機制。

    一個線程一旦挪用了隨意率性對象的 wait()辦法,就會變成非運轉狀況,直到另外一個線程挪用了統一個對象的 notify()辦法。為了挪用 wait()或許 notify(),線程必需先取得誰人對象的鎖。也就是說,線程必需在同步塊裡挪用 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();
     }
     }
    }
    
    

    期待線程將挪用 doWait(),而叫醒線程將挪用 doNotify()。當一個線程挪用一個對象的 notify()辦法,正在期待該對象的一切線程中將有一個線程被叫醒並許可履行(校注:這個將被叫醒的線程是隨機的,弗成以指定叫醒哪一個線程)。同時也供給了一個 notifyAll()辦法來叫醒正在期待一個給定對象的一切線程。

    如你所見,不論是期待線程照樣叫醒線程都在同步塊裡挪用 wait()和 notify()。這是強迫性的!一個線程假如沒有持有對象鎖,將不克不及挪用 wait(),notify()或許 notifyAll()。不然,會拋出 IllegalMonitorStateException 異常。

    (校注:JVM 是這麼完成的,當你挪用 wait 時刻它起首要檢討下以後線程能否是鎖的具有者,不是則拋出 IllegalMonitorStateExcept。)

    然則,這怎樣能夠?期待線程在同步塊外面履行的時刻,不是一向持有監督器對象(myMonitor 對象)的鎖嗎?期待線程不克不及壅塞叫醒線程進入 doNotify()的同步塊嗎?謎底是:切實其實不克不及。一旦線程挪用了 wait()辦法,它就釋放了所持有的監督器對象上的鎖。這將許可其他線程也能夠挪用 wait()或許 notify()。

    一旦一個線程被叫醒,不克不及連忙就加入 wait()的辦法挪用,直到挪用 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();
     }
     }
    }
    
    

    線程加入了它本身的同步塊。換句話說:被叫醒的線程必需從新取得監督器對象的鎖,才可以加入 wait()的辦法挪用,由於 wait 辦法挪用運轉在同步塊外面。假如多個線程被 notifyAll()叫醒,那末在統一時辰將只要一個線程可以加入 wait()辦法,由於每一個線程在加入 wait()前必需取得監督器對象的鎖。

    喪失的旌旗燈號(Missed Signals)

    notify()和 notifyAll()辦法不會保留挪用它們的辦法,由於當這兩個辦法被挪用時,有能夠沒有線程處於期待狀況。告訴旌旗燈號事後便拋棄了。是以,假如一個線程先於被告訴線程挪用 wait()前挪用了 notify(),期待的線程將錯過這個旌旗燈號。這能夠是也能夠不是個成績。不外,在某些情形下,這能夠使期待線程永久在期待,不再醒來,由於線程錯過了叫醒旌旗燈號。

    為了不喪失旌旗燈號,必需把它們保留在旌旗燈號類裡。在 MyWaitNotify 的例子中,告訴旌旗燈號應被存儲在 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()辦法在挪用 notify()前把 wasSignalled 變量設為 true。同時,留心 doWait()辦法在挪用 wait()前會檢討 wasSignalled 變量。現實上,假如沒有旌旗燈號在前一次 doWait()挪用和此次 doWait()挪用之間的時光段裡被吸收到,它將只挪用 wait()。

    (校注:為了不旌旗燈號喪失, 用一個變量來保留能否被告訴過。在 notify 前,設置本身曾經被告訴過。在 wait 後,設置本身沒有被告訴過,須要期待告訴。)

    假叫醒

    因為莫明其妙的緣由,線程有能夠在沒有挪用過 notify()和 notifyAll()的情形下醒來。這就是所謂的假叫醒(spurious wakeups)。無故端地醒過去了。

    假如在 MyWaitNotify2 的 doWait()辦法裡產生了假叫醒,期待線程即便沒有收到准確的旌旗燈號,也可以或許履行後續的操作。這能夠招致你的運用法式湧現嚴重成績。

    為了避免假叫醒,保留旌旗燈號的成員變量將在一個 while 輪回裡接收檢討,而不是在 if 表達式裡。如許的一個 while 輪回叫做自旋鎖(校注:這類做法要鄭重,今朝的 JVM 完成自旋會消費 CPU,假如長時光不挪用 doNotify 辦法,doWait 辦法會一向自旋,CPU 會消費太年夜)。被叫醒的線程會自旋直到自旋鎖(while 輪回)裡的前提變成 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 輪回裡,而不在 if 表達式裡。假如期待線程沒有收到旌旗燈號就叫醒,wasSignalled 變量將變成 false,while 輪回會再履行一次,促使醒來的線程回到期待狀況。

    多個線程期待雷同旌旗燈號

    假如你有多個線程在期待,被 notifyAll()叫醒,但只要一個被許可持續履行,應用 while 輪回也是個好辦法。每次只要一個線程可以取得監督器對象鎖,意味著只要一個線程可以加入 wait()挪用並消除 wasSignalled 標記(設為 false)。一旦這個線程加入 doWait()的同步塊,其他線程加入 wait()挪用,並在 while 輪回裡檢討 wasSignalled 變量值。然則,這個標記曾經被第一個叫醒的線程消除了,所以其他醒來的線程將回到期待狀況,直到下次旌旗燈號到來。

    不要在字符串常量或全局對象中挪用 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();
     }
     }
    }
    
    

    在空字符串作為鎖的同步塊(或許其他常量字符串)裡挪用 wait()和 notify()發生的成績是,JVM/編譯器外部會把常量字符串轉換成統一個對象。這意味著,即便你有 2 個分歧的 MyWaitNotify 實例,它們都援用了雷同的空字符串實例。同時也意味著存在如許的風險:在第一個 MyWaitNotify 實例上挪用 doWait()的線程會被在第二個 MyWaitNotify 實例上挪用 doNotify()的線程叫醒。這類情形可以畫成以下這張圖:

    起先這能夠不像個年夜成績。究竟,假如 doNotify()在第二個 MyWaitNotify 實例上被挪用,真正產生的事不過乎線程 A 和 B 被毛病的叫醒了 。這個被叫醒的線程(A 或許 B)將在 while 輪回裡檢討旌旗燈號值,然後回到期待狀況,由於 doNotify()並沒有在第一個 MyWaitNotify 實例上挪用,而這個恰是它要期待的實例。這類情形相當於激發了一次假叫醒。線程 A 或許 B 在旌旗燈號值沒有更新的情形下叫醒。然則代碼處置了這類情形,所以線程回到了期待狀況。記住,即便 4 個線程在雷同的同享字符串實例上挪用 wait()和 notify(),doWait()和 doNotify()裡的旌旗燈號還會被 2 個 MyWaitNotify 實例分離保留。在 MyWaitNotify1 上的一次 doNotify()挪用能夠叫醒 MyWaitNotify2 的線程,然則旌旗燈號值只會保留在 MyWaitNotify1 裡。

    成績在於,因為 doNotify()僅挪用了 notify()而不是 notifyAll(),即便有 4 個線程在雷同的字符串(空字符串)實例上期待,只能有一個線程被叫醒。所以,假如線程 A 或 B 被發給 C 或 D 的旌旗燈號叫醒,它會檢討本身的旌旗燈號值,看看有無旌旗燈號被吸收到,然後回到期待狀況。而 C 和 D 都沒被叫醒來檢討它們現實上吸收到的旌旗燈號值,如許旌旗燈號便喪失了。這類情形相當於後面所說的喪失旌旗燈號的成績。C 和 D 被發送過旌旗燈號,只是都不克不及對旌旗燈號作出回應。

    假如 doNotify()辦法挪用 notifyAll(),而非 notify(),一切期待線程都邑被叫醒並順次檢討旌旗燈號值。線程 A 和 B 將回到期待狀況,然則 C 或 D 只要一個線程留意到旌旗燈號,並加入 doWait()辦法挪用。C 或 D 中的另外一個將回到期待狀況,由於取得旌旗燈號的線程在加入 doWait()的進程中消除了旌旗燈號值(置為 false)。

    看過下面這段後,你能夠會想法應用 notifyAll()來取代 notify(),然則這在機能上是個壞主張。在只要一個線程能對旌旗燈號停止呼應的情形下,沒有來由每次都去叫醒一切線程。

    所以:在 wait()/notify()機制中,不要應用全局對象,字符串常量等。應當應用對應獨一的對象。例如,每個 MyWaitNotify3 的實例具有一個屬於本身的監督器對象,而不是在空字符串上挪用 wait()/notify()。

    以上就是關於Java 多線程,線程通訊的材料整頓,後續持續彌補相干材料,感謝年夜家對本站的支撐!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved