Java同步和線程交互的理解

Java的線程設計比較簡單,可是從操作系統層面的API來理解是,會存在問題。Java提供的線程設計和Unix/Linux系統的API設計思路稍有不同,在理解上也會存在問題。筆者使用Java多年,經常使用Java的多線程,確發現在編寫一個測試用例時發現自己並沒有深入理解Java的線程機制。本文通過簡明扼要的方式簡要說明並嘗試用圖的方式理解Java的多線程機制。
請看圖:
Java對象中多線程相關變量示意圖
簡單看圖中文章,應該可以理解Java的同步和線程交互的關鍵了。
Java的多線程是建立在synchronized關鍵字和Object對象的wait/notify/notifyAll方法上的。一般情況下,Java的多線程使用synchronized關鍵字保護競爭性變量或方法,從而實現簡單高效的多線程編程模型。考慮使用wait方法時,一般涉及複雜的等待通知機制,比如,生產/消費模型就需要用到該機制。
我們看看上圖,安裝Java的規範,Java的Object對象上具有一個Monitor(監控器)的變量,這個變量是一個計數器變量(我的理解,規範中沒有明確定義是否爲一個變量),通過synchronized關鍵字可以修改該變量。對對象調用synchronized,會給對象的監控器變量加一,如果值爲1,當前線程獲得對象的唯一使用權,這樣就相當於操作系統的互斥(Mutex)原語。如果由多個線程在同一個對象上使用synchronized,那麼後面的線程會繼續累加計數器,退出同步塊時,計數器減一,下一個線程可以獲得對象的唯一使用權。這就是synchronized關鍵字的作用。
再看下方的淺藍色方塊,我叫他等待集合(Wait sets),任何線程在對象上調用wait方法時,會把對象添加到等待集合中。如果對象的監控器計數器爲0(沒有給該對象加鎖),則,拋出IllegalMonitorStateException異常,因此,wait方法的調用必須在synchronized關鍵字的語句塊下執行。
注意:wait方法會給對象的監控器解鎖(關鍵的地方,不然其他線程就沒有辦法獲得對象的鎖了),並在返回前(無論是正常返回還是異常)又需要重新獲得監控器鎖。

synchronized(syncLock){
    syncLock.wait();
    // do somethings....
}

上面代碼,wait方法在語句塊中,本應該是一個整體操作的,現在線程需要等待一個外部操作完成,纔可以繼續執行。

注意,由於synchronized關鍵字是在對象上的等待,如果一個類有多個synchronized的方法,那麼所有的方法共享同一個監控器,那麼意味着一個方法的阻塞,會阻塞該實例的所有方法,如果是靜態類,那麼虛擬機內所有調用都必須同步調用任何一個方法。

由於線程會在InterruptException中醒來,Java規範建議wait方法採用循環的寫法來處理中斷異常。

boolean bFlag = false;
do{
  try{
    syncLock.wait();
    bFlag = true;
  }catch(InterruptException e){
    // continue waiting
  }
}while(!bFlag);

這樣,中斷時間就不會影響真正需要等待的事件了。當然,如果是自己的業務邏輯中斷,就需要更多的邏輯處理了。

本文如有不對的地方,請批評指正。
規範部分翻譯鏈接: Java 同步(Synchronization),等待(wait)通知(notify, notifyall)

發佈了95 篇原創文章 · 獲贊 63 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章