2011-10-26 102 views
2

我有一個列表,我在我的函數doMapOperation中的名稱爲synchronizedMap上同步。在這個函數中,我需要添加/刪除地圖中的項目並對這些對象執行昂貴的操作。我知道我不想在同步塊中調用昂貴的操作,但我不知道在執行這些操作時如何確保地圖處於一致狀態。什麼是正確的方法來做到這一點?收集昂貴操作的Java同步

這是因爲要避免在同步塊調用一個昂貴的操作我的,我敢肯定初始佈局是錯誤的:

public void doMapOperation(Object key1, Object key2) { 
    synchronized (synchronizedMap) { 

     // Remove key1 if it exists. 
     if (synchronizedMap.containsKey(key1)) { 
      Object value = synchronizedMap.get(key1); 
      value.doExpensiveOperation(); // Shouldn't be in synchronized block. 

      synchronizedMap.remove(key1); 
     } 

     // Add key2 if necessary. 
     Object value = synchronizedMap.get(key2); 
     if (value == null) { 
      Object value = new Object(); 
      synchronizedMap.put(key2, value); 
     } 

     value.doOtherExpensiveOperation(); // Shouldn't be in synchronized block. 
    } // End of synchronization. 
} 

我想爲這個問題的延續,你會怎麼做在一個循環?

public void doMapOperation(Object... keys) { 
    synchronized (synchronizedMap) { 

     // Loop through keys and remove them. 
     for (Object key : keys) { 
      // Check if map has key, remove if key exists, add if key doesn't. 
      if (synchronizedMap.containsKey(key)) { 
       Object value = synchronizedMap.get(key); 
       value.doExpensiveOperation(); // Shouldn't be here. 

       synchronizedMap.remove(key); 
      } else { 
       Object value = new Object(); 
       value.doAnotherExpensiveOperation(); // Shouldn't here. 

       synchronizedMap.put(key, value); 
      } 
     } 
    } // End of synchronization block. 
} 

感謝您的幫助。

+1

這裏沒有足夠的信息來了解在緩慢操作期間是否可以放棄鎖。關鍵問題是所有這些工作是否似乎都以原子方式發生在其他線程上?如果不是,哪些操作應該顯示爲原子?那麼操作是否必須在整個地圖上自動發生?換句話說,如果您可以確保單個密鑰上的操作是原子性的,那該怎麼辦? – erickson

回答

2

你可以做最昂貴的操作您的同步塊外,像這樣:

public void doMapOperation(Object... keys) { 
    ArrayList<Object> contained = new ArrayList<Object>(); 
    ArrayList<Object> missing = new ArrayList<Object>(); 

    synchronized (synchronizedMap) { 
     if (synchronizedMap.containsKey(key)) { 
      contained.add(synchronizedMap.get(key)); 
      synchronizedMap.remove(key); 
     } else { 
      missing.add(synchronizedMap.get(key)); 
      synchronizedMap.put(key, value); 
     } 
    } 

    for (Object o : contained) 
     o.doExpensiveOperation(); 
    for (Object o : missing) 
     o.doAnotherExpensiveOperation(); 
} 

唯一的缺點是,你可以在值進行操作,他們從synchronizedMap刪除後。

1

在第一個片段中:聲明if-clause中的兩個值,並將它們分配到if子句中。使if-clause同步,並調用外部的昂貴操作。

在第二種情況下做同樣的事情,但在循環內。 (在循環內同步)。當然,您可以在循環外只有一個​​語句,並且只需填寫要調用昂貴操作的對象的List即可。然後,在第二個循環中,在synchronized塊的外部,調用列表中所有值的操作。

+0

在單一方法中是否有任何反對具有多個同步塊的內容?在第二種情況下,如果您循環數百次,則會同步數百次。那不好嗎? –

+0

如果您需要同步一百個操作,那麼您需要一百個同步。檢查我的更新雖然 – Bozho

2

您可以爲您的synchronizedMap創建包裝,並確保像containsKey,removeput這樣的操作是同步方法。那麼只有訪問地圖纔會同步,而昂貴的操作可能發生在同步塊之外。

另一個優點是,如果操作調用另一個同步映射方法,則可以將昂貴的操作保留在同步塊之外,從而避免可能的死鎖風險。

+0

我知道包裝會幫助使這些方法在被調用時同步,但我也想確保在修改sychronizedMap的過程中映射不會在其他地方被修改。 –

1

實際上,只有一次同步命中就可以完成所有操作。第一次刪除可能是最簡單的。如果你知道這個對象存在,並且你知道remove是atomic,那麼爲什麼不移除它,如果返回的不是null,則調用昂貴的操作?

// Remove key1 if it exists. 
     if (synchronizedMap.containsKey(key1)) { 
      Object value = synchronizedMap.remove(key1); 
      if(value != null){ //thread has exclusive access to value 
       value.doExpensiveOperation(); 
      } 
     } 

對於放,因爲它是昂貴的,應該是原子,你幾乎不走運,需要同步訪問。我會推薦使用某種計算地圖。看看谷歌的集合和MapMaker

您可以創建的ConcurrentMap將根據你的關鍵,例如建造昂貴的對象

ConcurrentMap<Key, ExpensiveObject> expensiveObjects = new MapMaker() 
     .concurrencyLevel(32) 
     .makeComputingMap(
      new Function<Key, ExpensiveObject>() { 
      public ExpensiveObject apply(Key key) { 
       return createNewExpensiveObject(key); 
      } 
      }); 

這是simlpy的memoization

在這兩種形式在這些情況下,根本不需要使用​​(至少明確)

1

我們應該忘記小效率約97%的時間: 不成熟的優化是所有邪惡的根源。然而,我們不應該通過 提高我們在這個關鍵的3%的機會。一個好的程序員不會因爲這樣的推理而變得自滿,他會明智地在關鍵代碼中仔細查看 ;但只有在該代碼已被識別後 。 - Donald Knuth

你有一個方法doMapOperation()。如果此方法繼續進行塊同步,那麼您的性能如何?如果你不知道,那麼你怎麼知道什麼時候你有一個很好的解決方案?即使從地圖中刪除了您的昂貴操作,您是否準備好處理多次通話?

我並不是想居住,因爲也許你理解現在的問題比你已經傳達的更好,但是看起來你正在跳入一個優化級別,你可能沒有準備好,可能會沒有必要。

1

如果你沒有在Map空值,你不需要containsKey()呼叫都:您可以使用Map.remove(),既除去該項目,並告訴你是否在那裏。所以,你的synchronized塊的真實內容只需要是這樣的:

Object value = Map.remove(key); 
if (value != null) 
    value.doExpensiveOperation(); 
else 
{ 
    value = new Value(); 
    value.doExpensiveOperation(); 
    map.put(key,value); 
} 

如果昂貴的操作本身並不需要同步,也就是說,如果你不介意的地圖的其他客戶端看到的價值,同時它正在運行,您可以進一步簡化爲:

Object value = Map.remove(key); 
if (value == null) 
{ 
    value = new Value(); 
    map.put(key,value); 
} 
value.doExpensiveOperation(); 

並且同步塊可以在昂貴的操作之前終止。

相關問題