本文大概意思都是都下邊連接文章轉換過來的,沒有進行一字一句的翻譯,只是把大概意思整裏出來。
html
http://tutorials.jenkov.com/java-concurrency/thread-signaling.htmljava
線程信號的目的是爲了線程之間相互通訊。線程信號可使線程等待另其餘線程信號,例如thread B 或許等待從thread A 發出的數據準備處理信號。多線程
線程之間能夠經過共享對象來相互發送信號。Thread A 能夠經過在synchronized同步塊裏設置變量 hasDataToProcess爲true ,線程B一樣在synchronized同步塊裏讀取hasDataToProcess的值來肯定是否有數據可讀。this
下面是一個簡單的信號對象spa
public class MySignal{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; } }
thread A 與 thread B 必要經過同一個MySignal 實例來進行通訊。若是 A B 引用了不一樣的MySignal實例,它們之間將不會接收到相互的信號。.net
因爲thread B 一直在等待是否有數據能夠處理,若是採用上面的MySignal實例,那麼thread B 必須一直循環調用hasDataToProcess來判斷是否有數據處理。這樣就會產生忙等,消耗大量的CPU。線程
忙等除了在平均時間較短的狀況下比較有效,其餘狀況不能有效的利用CPU。若是線程在收到信號以前能夠sleep,翻譯
或者變成inactive 狀態。java 內嵌了使等待線程變成inactive狀態的機制。使用java.lang.Object 上面的wait(),notify(),code
notifyAll()能夠實現。當一個線程在任意object 上調用wait(),它會變成inactive狀態,直到有其餘線程在該object上調用notify(),該線程纔會繼續執行。爲了調用wait 或者notify ,調用線程必須獲取在object上的鎖。換句話說調用線程必須在同步塊裏調用wait ,notify,notifyAll。htm
下面是改造版的信號類MyWaitNotify
public class MyWaitNotify{ Object myMonitorObject = new Object(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } } }
等待線程能夠調用doWait,發出通知的線程能夠調用doNotify。當一個線程在某個object上調用notify,全部在該object等待的線程,只有一個線程將會被喚醒繼續執行代碼。若是調用notifyAll能夠喚醒該對象上全部等待的線程。全部的等待線程 ,通知線程都必須在synchronized 中調用wait,notify ,notifyAll。這是強制的。一個線程若是沒有獲取一個object上的鎖,將不能調用這幾個方法。不然會拋出IllegalMonitorStateException異常。一旦一個線程調用wait ,它將釋放它所持有的monitor object上的鎖。這樣就能夠容許其餘線程調用同步塊中的wait 或者notify 。當一個線程未離開notify同步塊以前,喚醒的線程不能退出wait調用塊,由於沒有得到monitor object 上的鎖。
notify,notifyAll 不保存對它們的方法調用當沒有線程等待在該monitor object。notify 信號就丟死了。所以,若是一個線程在調用wait以前調用notify,信號將會被等待線程丟失。這樣在一些案例中將致使等待線程一直在等待,不會醒來,由於通知信號被丟失了。爲了不信號的丟失,信號能夠被存儲在signal類中。
下面是會存儲信號的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(); } } }
虛假喚醒指的是即便線程沒有調用監視器對象上的notify或者notifyAll,等待線程就醒了。喚醒可能沒有任何緣由的發生。若是一個虛假喚醒發生在MyWaitNofiy2中的doWait()中,等待線程在沒有接收到一個恰當的信號就開始執行。這會致使嚴重的後果。下邊把MyWaitNotify2中的if 判斷改成while循環,只有wasSignalled狀態改變,才認爲是notify真正的被髮出。
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(); } } }
改爲while循環後,即便發生虛假喚醒,若是wasSignalled的值沒有被改變,那麼線程將再次調用wait(),使線程變爲inactive狀態。
while 循環一樣在多線程等待的狀況中還是一個很好的解決方案。當監視器上的notifyAll被調用時,只有其中一個線程會被容許繼續執行,其餘的將會被再次等待,由於其餘線程得到鎖後,wasSignalled上的信號已經被第一個喚醒的線程擦除掉了。
當線程把一個把常量字符串看成監視器對象時,會出現異常。緣由是JVM/Compiler內部會把常量字符串轉換爲同一對象對待。這意味着即便你有兩個不一樣的MyWaitNotify實例,它們裏面的監視器對象將會是同一個字符對象。這意味着若是一個線程在第一信號實例調用wait方法,可能將會被另外一信號實例上notify的調用喚醒。所以不要把全局對象,string 常量看成監視器對象用。