2013-08-22 37 views
3

請解釋下面的語句有關Hortsmann,康奈從「核心Java」的同步塊(9版,p.865。):爲什麼不推薦客戶端鎖定?

GET和Vector類的設置方法是同步的,但沒有按幫不了我們。
...
但是,我們可以劫持鎖:

public void transfer(Vector<Double> accounts, int from, int to, int amount) 
{ 
    synchronized (accounts) 
    { 
     accounts.set(from, accounts.get(from) - amount); 
     accounts.set(to, accounts.get(to) + amount); 
    } 
    //... 
} 

這種方法的工作原理,但它是完全依賴於Vector類使用其所有mutator方法的內部鎖的事實。

爲什麼同步取決於上述事實?如果一個線程A 擁有對帳戶的鎖定,則其他線程無法獲得相同的鎖定。它不依賴於Vector用於其增變器方法的鎖。

我能想到的唯一可能的解釋是以下一種。讓線程A擁有鎖定帳戶。如果Vector爲它的set/get使用另一個鎖,那麼線程A必須獲得一個額外的鎖來繼續set/get,並且由於某種原因這是不可能的(線程可以同時持有2個不同的鎖嗎?)。

這個解釋對我來說看起來並不合理,但我沒有其他任何東西。 我錯過了什麼?

回答

5

如果線程A擁有賬號上的鎖定,則其他線程無法獲得相同的鎖定。它不依賴於Vector用於其增變器方法的鎖。

但是,如果Vector爲自己的同步使用完全不相關的鎖,那麼你的鎖對象將是非常沒有意義的。這樣的代碼不會同步:

x.transfer(vector, 100, 100, 100); // uses your lock 

vector.add(100); // uses Vector's own, unrelated lock 

如果所有的代碼經過自己的方法(使用你的鎖),沒有人訪問向量的方法直接,那麼你的罰款。 (但是,你根本不需要使用Vector的內置同步,並且可以使用ArrayList)。

這些鎖只在所有相關代碼路徑都使用它們時才起作用。通常涉及多種方法,他們需要使用相同的一組鎖來正確地「與對方交談」。程序員應該確保這一點。

讓線程A擁有鎖定帳戶。如果Vector爲它的set/get使用另一個鎖,那麼線程A必須獲得一個額外的鎖來繼續set/get,並且由於某種原因這是不可能的(線程可以同時持有2個不同的鎖嗎?)。

這不是不可能的,線程A可以容納任意數量的鎖。但是,在上面的訪問模式中,線程A保持第一個鎖是毫無意義的,因爲線程B只使用內置的Vector鎖時甚至不會嘗試鎖定它。

2

這種方法是有效的,但它完全依賴於Vector類爲所有mutator方法使用內部鎖的事實。

這是試圖解釋你鎖定在accountsVector鎖定在同一個對象上。這意味着其他線程將更改爲accountsVector將被鎖定,您將不會有競爭狀態。

如果你不同意這個鎖,那麼你將有一個競爭條件,因爲有4個操作是​​塊裏想的是:

  1. 從帳戶獲取的當前值
  2. 設置從帳戶與遞減值
  3. 獲取的價值佔
  4. 設置考慮到具有增加的值

由於存在鎖定,我假設其他線程正在修改後臺中的其他帳戶。如果Vector假設性地改變了他們的內部鎖定策略,其他線程可以在這個過程的中間對來自或去往賬戶進行更改,並搞砸會計。例如,如果發件人賬號在#1和#2之間遞增,那麼由於轉賬,該值將被覆蓋。

依賴於這樣的類的內部鎖定範例是非常糟糕的形式。這意味着如果Vector決定改變它的鎖定機制(是的,我知道它不會),那麼你的代碼就會有競爭狀態。更有可能的是,另一個程序員(或未來你)決定將accounts更改爲不同的Collection,它使用了不同的鎖定機制,代碼將會中斷。你不應該依賴於一個類的內部行爲,除非它被特別記錄。

如果您需要防止出現這種競爭狀況,那麼您應該執行​​左右的鎖定,以便訪問Vector

順便說一句,你不應該使用Vector了。如果您需要同步列表,請使用Collections.synchronizedList(new ArrayList<Double>);或Java 5中引入的新併發類之一。

+0

「如果向量決定改變它的鎖定機制」雖然這是不可能的。它如何同步顯然是API的一部分,他們不會打破這一點。 – Thilo

+0

在Vector的Javadoc中,它提到了你可以依賴所有被同步的方法。如果將'Vector'切換到使用內部的'private final Object lock',它們不會相同嗎?我知道他們不會改變它,但這是我反對的模式。 @Thilo。 – Gray

+1

@Thilo實際上,他們在書中進一步提到Vector並未做出這樣的承諾。 –

1

我還沒有讀過這本書,但我認爲他們想說的是每個Vector方法都是同步的(獨立的),即使這可以保護Vector免受損壞,但它不會保護它可能存儲在其中的信息(尤其是因爲業務規則,數據模型,數據結構設計或您想要調用它們)。

示例:方法transfer是天真地執行,相信如果它Vectorset方法是同步的,那麼一切都很好。

public void transfer(Vector<Double> accounts, int from, int to, int amount) { 
    accounts.set(from, accounts.get(from) - amount); 
    accounts.set(to, accounts.get(to) + amount); 
} 

如果2個線程(T2和T3)調用transfer在同一時間,比方說,同樣的from帳戶(A1)和平衡$ 1,500不同to賬戶(A2和A3)會發生什麼用餘額$ 0?說出100美元到A2和1200美元到A3?

  1. T2:accounts.get('A1')被檢索並減去100。結果= 1,400,但它的尚未儲存回去
  2. T3:檢索accounts.get('A1')(仍爲1,500)並減去1,200。結果= 300
  3. T3:將結果存回:accounts.get('A1')(如果執行)將生成300.
  4. T2:存儲以前計算的結果。如果執行,accounts.get('A1')將產生1,400。
  5. T2:accounts.get('A2')被檢索(值0),100被添加並被存回。 accounts.get('A2')(如果執行)將產生100.
  6. T3:accounts.get('A3')被檢索(值0),1,200被添加並被存回。 accounts.get('A3')(如果執行)將產生1200。

於是,我們開始A1 + A2 + A3 = 1500 + 0 + 0 = 1500,而做這些內部轉賬後,我們有A1 + A2 + A3 = 1400 + 100 + 1200 = 2,700元。顯然有些東西在這裏不起作用。什麼?在步驟1和步驟4之間.T2保持A1的平衡,並且沒有(不能)檢測到T3也在減少它,至少在概念層面。

這當然不會發生在每次運行中。但這是邪惡的,因爲如果隱藏在幾十萬行代碼中,再現(以及發現和重新測試)這個問題將會很困難。

但是請注意,即使accounts方法從未同時被調用,上述情況也會發生。甚至沒有嘗試。實際上,accounts作爲Vector沒有損壞,但它作爲我們的數據結構已損壞。

這就是那句

get和Vector類的設置方法是同步的,但這並不能幫助我們。

指的是。

如果說對於提出的解決方案是

這種方法的工作原理,但它是完全依賴於Vector類使用其所有mutator方法的內部鎖的事實。

,作者肯定假設除了轉移方法之外,account還有其他用途。例如:添加賬戶,刪除賬戶等。從這個意義上說,幸運的是,這些方法也在矢量上同步。如果它們在一個內部對象中同步,則系統的開發人員需要將accounts包裝在輔助同步層中,這次是基於accounts本身或任何其他常見對象。

最後,關於

如果一個線程A擁有的賬戶鎖定,沒有其他線程可以獲取同樣的鎖。它不依賴於Vector用於其增變器方法的鎖。

這可能恰恰是這樣一個觀點:所有需要對數據結構進行互斥訪問的類需要就他們將要訪問的對象達成一致synchronize。這個案例非常簡單。在更復雜的情況下,選擇這個對象不是微不足道的。在許多情況下,相反,如果將某個對象放在其他對象上,則會創建一個特殊的「鎖定」對象。但是這有一個限制:整個數據結構一次只能更新一次。在出現這種問題的應用程序中,需要定義更詳細的鎖定策略,開發人員可能難以確定哪些對象可能被鎖定,以及哪些對象應鎖定在每種可能的情況下。這些策略還需要關注死鎖和競態條件的可能性。

相關問題