2017-06-22 19 views
1

通常我會鎖定類似下面的關鍵部分。使用密鑰的同步訪問的代碼塊

public class Cache { 
    private Object lockObject = new Object(); 

    public Object getFromCache(String key) { 
     synchronized(lockObject) { 

      if (cache.containsKey(key)) { 
       // key found in cache - return cache value 
      } 
      else { 
       // retrieve cache value from source, cache it and return 
      } 
     } 
    } 
} 

的想法是我避免出現競爭狀況可能導致數據源被擊中多次與鍵被添加到緩存中多次。

現在如果兩個線程在大約相同的時間不同緩存鍵進來,我仍然會阻礙之一。

假設鍵獨一無二的 - 將鎖仍然鎖定在重點工作?

我認爲它不會工作,因爲我知道對象引用應該是相同的鎖才能生效。我想這歸結於它如何檢查平等。

public class Cache { 

    public Object getFromCache(String key) { 
     synchronized(key) { 

      if (cache.containsKey(key)) { 
       // key found in cache - return cache value 
      } 
      else { 
       // retrieve cache value from source, cache it and return 
      } 
     } 
    } 
} 
+0

號你需要保護的資源的緩存,而不是重點。你的建議沒有意義。 – EJP

+1

通常,密鑰會散列到鎖對象的數組中以避免實例問題。這被稱爲條帶化,其中條帶(資源)通過該鎖保護免受任何突變。在你的緩存的例子中,這被稱爲memoization來避免[緩存踩踏](https://en.wikipedia.org/wiki/Cache_stampede),通過鎖定內部散列表項來完成。爲了避免鎖定讀取,這將使用樂觀的獲取和回退(雙重檢查鎖定)。 [咖啡因](https://github.com/ben-manes/caffeine)和其他緩存實現這種技術。 –

+0

謝謝Ben,你的解釋很有用。我將介紹在密鑰上散列的鎖數組。 – Avner

回答

-1

每個對象都有一個同步工作的隱式監視器。字符串對象可能在堆中創建,並且對於同一組字符(如果使用new創建的)或者可能來自池,也可能不同。只有在同一個對象上進行同步時,兩個線程纔會使用同步塊訪問關鍵部分。

同步上字符串文字實在是一個糟糕的主意。來自池的字符串文字是共享的。試想一下,如果在代碼的兩個不同部分中,您有兩個同步部分,並且您在兩個String引用上同步,但是使用同一組字符來初始化字符串,如果使用了來自池的String,則兩個位置都將是同一個對象。儘管這兩個地方可能有不同的業務環境,但您的應用程序可能會被掛起。調試也很困難。

對於特定的意志爲目的的問題,如果同步上鍵來完成解決。

你要避免兩個線程試圖不讀緩存的最新值來寫。每個條目您都有不同的密鑰。假設一個線程1和線程2想要訪問相同的密鑰,那麼同一個密鑰對象上的同步將阻止它們進入同步的塊。同時,如果一個thread3想要訪問另一個不同的密鑰,那麼它可以很好地實現。在這裏,我們看到,與單個通用對象相比,所有密鑰的讀寫操作都更快。到目前爲止這麼好,但如果你要保存一個數組或其他類似的非線程安全數據結構來存儲緩存值,問題就會出現。同時寫入(對於兩個或更多不同的鍵)可能會導致一個寫操作被另一個寫操作在相同索引處覆蓋。

因此,它取決於緩存數據結構的實現方式,以便如何在多線程環境中更好地進行讀寫準備。

+0

不知道爲什麼這是downvoted,但我認爲它證實了我的期望,該實例必須是相同的。 – Avner

+1

@awi我低估了它,因爲它沒有解決這個問題,這是我在我的評論中提到的。你會錯誤地認爲這是你實際詢問的正確答案。 – EJP

+0

@avvi從某種程度上說,我自己同意EJP的觀點,在修改之後,我發現我之前錯過了幾件事情。我編輯了答案,並嘗試先前觸摸錯過的區域。 –