跳轉到內容

基礎同步

75% developed
來自華夏公益教科書

導航 併發程式設計 主題: v  d  e )


在多執行緒環境中,當多個執行緒可以訪問和修改資源時,結果可能是不可預測的。例如,讓我們有一個計數器變數,它由多個執行緒遞增。

注意!同步是一個模糊的術語。它不包括讓所有執行緒同時執行相同的程式碼部分。恰恰相反。它阻止任何兩個執行緒同時執行相同的程式碼部分。它同步了第一個處理的結束和第二個處理的開始。

Example 程式碼部分 1.1:計數器實現
int counter = 0;
...
counter += 1;

上面的程式碼由以下子操作組成

  • 讀取 ; 讀取變數 counter
  • 新增 ; 在值上加 1
  • 儲存 ; 將新值儲存到變數 counter

假設有兩個執行緒需要執行該程式碼,如果 counter 變數的初始值為零,我們期望操作完成後值為 2。

執行緒 1   執行緒 2
         
讀取 0   讀取 0
         
新增 1   新增 1
         
儲存 1   儲存 1
         

在上面的情況下,執行緒 1 的操作丟失了,因為執行緒 2 覆蓋了它的值。我們希望執行緒 2 等待執行緒 1 完成操作。請參見下文

執行緒 1   執行緒 2
         
讀取 0   阻塞
         
新增 1   阻塞
         
儲存 1   解除阻塞
         
  讀取 1
     
  新增 1
     
  儲存 2
     
臨界區
在上面的示例中,程式碼 counter+=1 必須在任何給定時間僅由一個執行緒執行。這稱為臨界區。在多執行緒環境中程式設計時,我們必須識別所有屬於臨界區的程式碼部分,並確保在任何給定時間只有一個執行緒可以執行這些程式碼。這就是同步。
同步執行緒
執行緒訪問臨界區程式碼必須線上程之間同步,即確保在任何給定時間只有一個執行緒可以執行它。
物件監視器
每個物件都有一個物件監視器。基本上它是一個訊號量,指示臨界區程式碼是否正在被執行緒執行。在執行臨界區之前,執行緒必須獲得一個物件監視器。一次只有一個執行緒可以擁有物件的監視器。
執行緒可以透過以下三種方式之一成為物件監視器的所有者
  • 透過執行該物件的同步例項方法。請參見 synchronized 關鍵字。
  • 透過執行在該物件上同步的同步語句的主體。請參見 synchronized 關鍵字。
  • 對於 Class 型別的物件,透過執行該類的同步靜態方法。
物件監視器負責同步,那麼為什麼我們需要“wait() 和 notify() 方法”呢?
對於同步,我們實際上並不需要它們,但是對於某些情況來說,使用它們會很好。一個友善體貼的執行緒會使用它們。在執行臨界區期間,執行緒可能會卡住,無法繼續執行。這可能是因為它正在等待 I/O 和其他資源。無論如何,執行緒可能需要等待很長時間。對於執行緒來說,持有物件監視器並阻止其他執行緒完成工作是自私的。因此,執行緒透過呼叫物件上的 wait() 方法進入“等待”狀態。它必須是執行緒從其獲得物件監視器的相同物件。
另一方面,執行緒應該只在至少存在一個其他執行緒會在資源可用時呼叫 notify() 方法的情況下呼叫 wait() 方法,否則執行緒將永遠等待,除非指定了一個時間間隔作為引數。
讓我們做一個類比。你去商店買一些東西。你在櫃檯排隊,你得到售貨員的注意 - 你得到她的“物件監視器”。你要求你要的商品。一件商品需要從倉庫調入。這需要超過五分鐘,所以你釋放售貨員(將她的“物件監視器”還給她),這樣她就可以為其他顧客服務。你進入等待狀態。假設還有五個其他顧客正在等待。另一個售貨員從倉庫調入商品。當她這樣做時,她會引起第一個售貨員的注意,獲得她的物件監視器,並通知一個或所有等待的顧客,因此等待的顧客醒來,再次排隊以引起第一個售貨員的注意。
注意等待的顧客和從倉庫調入商品的售貨員之間的同步。這是一種生產者-消費者同步。
還要注意只有一個物件監視器,屬於第一個售貨員。在等待通知發生之前,必須首先獲得該物件監視器/售貨員的注意。


final void wait() 方法
當前執行緒必須擁有此物件的監視器。執行緒釋放對該監視器的所有權,並等待另一個執行緒透過呼叫 notify 方法或 notifyAll 方法來通知等待此物件的監視器的執行緒喚醒。然後,執行緒等待它能夠重新獲得對監視器的所有權並恢復執行。
final void wait(long time)
與 wait 相同,但執行緒在指定的時間段過去後喚醒,無論是否收到通知。
final void notify()
此方法只能由擁有此物件的監視器的執行緒呼叫。喚醒一個正在等待此物件的監視器的執行緒。如果多個執行緒正在等待此物件的監視器,則其中一個執行緒會被選擇喚醒。選擇是任意的,並由實現決定。執行緒透過呼叫其中一個 wait 方法來等待物件的監視器。
喚醒的執行緒將無法繼續執行,直到當前執行緒釋放對該物件的鎖。喚醒的執行緒將與任何可能正在積極競爭同步到該物件的執行緒以通常的方式競爭;例如,喚醒的執行緒在成為下一個鎖定該物件的執行緒方面沒有任何可靠的優勢或劣勢。
final void notifyAll()
notify()相同,但它喚醒所有正在等待該物件的監視器的執行緒。


sleep() 和 wait() 方法有什麼區別?
Thread.sleep(millis)
這是 Thread 類的靜態方法。導致當前執行的執行緒休眠(暫時停止執行)指定毫秒數。執行緒不會失去對任何監視器的所有權。這意味著如果執行緒擁有物件監視器,所有其他需要該監視器的執行緒將被阻塞。無論執行緒是否有監視器,都可以呼叫此方法。
wait()
此方法繼承自Object 類。執行緒必須首先獲得該物件的監視器才能呼叫 wait() 方法。wait() 方法會釋放物件監視器,因此它不會阻止其他等待該物件監視器的執行緒。


Clipboard

待辦事項
新增一些練習,類似於變數 中的練習


華夏公益教科書