對特殊資源——**物件中的記憶體**——Java 提供內建機制防止衝突。通常將資料設為 private,只透過方法存取,故只需將特定方法設為 synchronized 即可有效防止衝突。**任何時刻,只有一個執行緒可調用特定物件的一個同步方法**(雖該執行緒可調用多個物件的同步方法)。
synchronized void f() { /* ... */ }
synchronized void g() { /* ... */ }
每個物件包含一把**鎖(監視器)**,自動成為物件一部分。調用任何同步方法 → 物件被鎖定,不可再調用該物件其他同步方法,除非第一個方法完成並釋放鎖。上例中,為物件調用 f() 後,不可為同物件調用 g(),除非 f() 完成釋放鎖。**該物件所有同步方法共享一把鎖**,防止多執行緒同時寫入共用記憶體。
每個類也有自己的鎖(作為 Class 物件一部分),synchronized static 方法可在類範圍內互相鎖定,防護 static 資料。
//: Sharing2.java
class TwoCounter2 extends Thread {
// ... 介面元件 ...
public synchronized void run() { // 整個 run 同步
while (true) {
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
try { sleep(500); } catch (InterruptedException e){}
}
}
public synchronized void synchTest() { // 同步檢查方法
Sharing2.incrementAccess();
if(count1 != count2) l.setText("Unsynched");
}
}
class Watcher2 extends Thread {
public void run() {
while(true) {
for(int i = 0; i < p.s.length; i++) p.s[i].synchTest();
try { sleep(500); } catch (InterruptedException e){}
}
}
}
因整個 run() 同步,且必須為每個物件運行 run(),**鎖永遠不會打開**,synchTest() 永不被調用。accessCount 根本不變化。
只將 run() 中關鍵部分隔離。想隔離的部分稱「關鍵區域」,用不同方式使用 synchronized 關鍵字:
synchronized(syncObject) {
// 此代碼同一時間只能被一個執行緒存取,假設所有執行緒尊重 syncObject 的鎖
}
進入同步區塊前必須在 syncObject 取得鎖。若其他執行緒已取得,區塊不可進入,必須等待鎖釋放。
public void run() {
while (true) {
synchronized(this) { // 以當前物件為鎖
t1.setText(Integer.toString(count1++));
t2.setText(Integer.toString(count2++));
}
try { sleep(500); } catch (InterruptedException e){}
}
}
這是對 Sharing2.java 唯一修改。現在兩計數器永不脫離同步,且 run() 執行期間仍給 Watcher 足夠存取權限。
獲取鎖非「廉價」——單次方法調用代價(進入/退出,不執行主體)至少累加 4 倍,視方案可能更高。若知某方法不會衝突,**最明智做法是移除 synchronized**。