簡體   English   中英

Java synchronized塊與Collections.synchronizedMap

[英]Java synchronized block vs. Collections.synchronizedMap

在下面的代碼設置為正確同步的調用synchronizedMap

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

根據我的理解,我需要addToMap()的synchronized塊來阻止另一個線程在調用put()之前調用remove()containsKey() put()但是我不需要doWork()的synchronized塊,因為另一個在remove()返回之前,線程無法進入addToMap()的synchronized塊,因為我最初使用Collections.synchronizedMap()創建了Map。 那是對的嗎? 有一個更好的方法嗎?

Collections.synchronizedMap()保證您要在地圖上運行的每個原子操作都將被同步。

但是,必須在塊中同步在地圖上運行兩個(或更多)操作。 是的 - 您正在正確同步。

如果您使用的是JDK 6,那么您可能需要查看ConcurrentHashMap

請注意該類中的putIfAbsent方法。

您的代碼中可能存在細微的錯誤。

[ 更新:因為他正在使用map.remove(),所以這種描述並不完全有效。 我第一次錯過了這個事實。 :(感謝問題的作者指出這一點。我將其余部分保留原樣,但改變了主要聲明,說有可能存在錯誤。]

doWork()中,您可以以線程安全的方式從Map獲取List值。 然而,之后,您在不安全的情況下訪問該列表。 例如,一個線程可能正在使用doWork()中的列表,而另一個線程在addToMap()中調用synchronizedMap.get(key).add(value 這兩個訪問不同步。 經驗法則是集合的線程安全保證不會擴展到它們存儲的鍵或值。

您可以通過在地圖中插入同步列表來解決此問題

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

或者,您可以在doWork()中訪問列表時在地圖上進行同步:

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

最后一個選項會稍微限制並發性,但IMO會更加清晰。

另外,關於ConcurrentHashMap的快速說明。 這是一個非常有用的類,但並不總是適用於同步HashMaps的替代品。 引用其Javadocs,

在依賴於線程安全但不依賴於其同步細節的程序中,此類可與Hashtable完全互操作。

換句話說,putIfAbsent()非常適合原子插入,但不保證在該調用期間地圖的其他部分不會改變; 它只保證原子性。 在您的示例程序中,您依賴於put()s以外的(同步)HashMap的同步詳細信息。

最后一件事。 :)來自Java Concurrency in Practice的這句精彩報價總能幫助我設計調試多線程程序。

對於可由多個線程訪問的每個可變狀態變量,必須在保持相同鎖的情況下執行對該變量的所有訪問。

是的,您正在正確同步。 我將更詳細地解釋這一點。 只有在必須依賴於在synchronizedMap對象上的方法調用序列中的后續方法調用中的先前方法調用的結果時,才必須在synchronizedMap對象上同步兩個或多個方法調用。 我們來看看這段代碼:

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
    }
}

在這段代碼中

synchronizedMap.get(key).add(value);

synchronizedMap.put(key, valuesList);

方法調用依賴於前一個的結果

synchronizedMap.containsKey(key)

方法調用。

如果方法調用序列未同步,則結果可能是錯誤的。 例如, thread 1正在執行方法addToMap()thread 2正在執行方法doWork() 。在synchronizedMap對象上調用方法的順序可能如下所示: Thread 1執行了方法

synchronizedMap.containsKey(key)

結果是“ true ”。 之后,操作系統將執行控制切換到thread 2並且已執行

synchronizedMap.remove(key)

之后,執行控制已切換回thread 1並且例如已執行

synchronizedMap.get(key).add(value);

認為synchronizedMap對象包含key並且將拋出NullPointerException因為synchronizedMap.get(key)將返回null 如果synchronizedMap對象上的方法調用序列不依賴於彼此的結果,那么您不需要同步序列。 例如,您不需要同步此序列:

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

這里

synchronizedMap.put(key2, valuesList2);

方法調用不依賴於前面的結果

synchronizedMap.put(key1, valuesList1);

方法調用(它不關心某些線程是否干擾了兩個方法調用,例如刪除了key1 )。

這對我來說是正確的。 如果我要改變任何東西,我會停止使用Collections.synchronizedMap()並以相同的方式同步所有內容,只是為了讓它更清晰。

另外,我會替換

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

你同步的方式是正確的。 但是有一個問題

  1. Collection框架提供的同步包裝器確保方法調用即add / get / contains將互斥。

但是在現實世界中,您通常會在放入值之前查詢地圖。 因此,您需要執行兩個操作,因此需要同步塊。 所以你使用它的方式是正確的。 然而。

  1. 您可以在Collection框架中使用Map的並發實現。 'ConcurrentHashMap'的好處是

一個。 它有一個API'putIfAbsent',它會以更有效的方式執行相同的操作。

它的效率:d CocurrentMap只是鎖定鍵,因此它不會阻擋整個地圖的世界。 你在哪里阻止了鍵和值。

C。 您可能已經在代碼庫中的其他位置傳遞了地圖對象的引用,您的/其他開發人員可能會錯誤地使用它。 即他可能只是添加()或get()而不鎖定地圖的對象。 因此,他的調用不會與您的同步塊互斥。 但是使用並發實現可以讓您高枕無憂,它永遠不會被錯誤地使用/實現。

查看Google CollectionsMultimap ,例如本演示文稿的第28頁。

如果由於某種原因無法使用該庫,請考慮使用ConcurrentHashMap而不是SynchronizedHashMap ; 它有一個漂亮的putIfAbsent(K,V)方法,你可以用原子方式添加元素列表(如果它還沒有)。 此外,如果您的使用模式需要,請考慮使用CopyOnWriteArrayList作為地圖值。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM