2013-03-19 50 views
2

我試圖找到一種方法來以原子方式在ConcurrentHashMap上執行多個操作。原子執行多個操作

我的邏輯是這樣的:

if (!map.contains(key)) { 
    map.put(key, value); 

    doSomethingElse(); 
} 

我知道有是putIfAbsent方法。但是如果我使用它,我仍然無法以原子方式調用doSomethingElse

除了訴諸同步/客戶端鎖定之外,還有什麼辦法可以做到嗎?

如果有幫助,在我的情況下,doSomethingElse會非常複雜,涉及創建和啓動一個線程,查找我們剛剛添加到地圖中的鍵。

+0

所以你想在執行doSomethingElse之前阻止線程訪問映射(並且看到新的鍵)? – assylias 2013-03-19 13:43:12

+0

是的。因爲如果我讓另一個線程在執行'doSomethingElse'之前看到新的鍵值,它可能會調用'doSomethingElse'也會啓動一個單獨的線程。 – adarshr 2013-03-19 13:45:05

+1

但是,如果您使用'putIfAbsent'而不是'if!contains then put',則只有一個投入呼叫會成功 - 這是否解決了爭用? – 2013-03-19 13:46:48

回答

3

這會工作,除非你想要把所有的線程等待,直到第一個成功的線程放入地圖..

if(map.get(key) == null){ 

    Object ret = map.putIfAbsent(key,value); 
    if(ret == null){ // I won the put 
    doSomethingElse(); 
    } 
} 

現在,如果許多線程具有相同key唯一一個把贏,只有一個將doSomethingElse()

+1

是的,那正是我想要的行爲。 'doSomethingElse'只能在投放成功時執行,無論它是哪個線程。其他線程必須簡單地掉出'if'。 – adarshr 2013-03-19 14:03:28

5

如果有幫助,在我的情況下,doSomethingElse會非常複雜,涉及創建和啓動一個線程,該線程會查找我們剛剛添加到地圖中的鍵。

如果是這樣的話,您通常必須進行外部同步。

在某些情況下(這取決於doSomethingElse()預計地圖的狀態是,什麼其他線程可能會做地圖),以下也可工作:

if (map.putIfAbsent(key, value) == null) { 
    doSomethingElse(); 
} 

這將確保只對於任何給定的密鑰,一個線程進入doSomethingElse()

+0

確實如此,但是它不會破壞首先使用'ConcurrentHashMap'的目的嗎? – adarshr 2013-03-19 13:46:43

+0

是的,但是使用'if!contains then put'''' ConcurrentHashMap'不是慣用的,這可能是你的問題 – 2013-03-19 13:47:47

+2

@adarshr - 它可能會破壞你的**目的......但那是因爲你期望CHM做一些顯然不能/不能做的事情。 – 2013-03-19 13:48:12

2

如果您的設計要求將地圖訪問和其他操作分組,而沒有其他人訪問地圖,則您別無選擇,只能將其鎖定。也許設計可以重新審視以避免這種需求?

這也意味着對地圖的所有其他訪問都必須在同一個鎖後面進行序列化。

1

您可能每個條目都保留鎖定。這將允許併發非鎖定更新,除非兩個線程嘗試訪問相同的元素。

class LockedReference<T> { 
    Lock lock = new ReentrantLock();; 
    T value; 
    LockedReference(T value) {this.value=value;}  
} 

LockedReference<T> ref = new LockedReference(value); 
ref.lock.lock(); //lock on the new reference, there is no contention here 
try { 
    if (map.putIfAbsent(key, ref)==null) { 
    //we have locked on the key before inserting the element 
    doSomethingElse(); 
    } 
} finally {ref.lock.unlock();} 

以後

Object value; 
while (true) { 
    LockedReference<T> ref = map.get(key) 
    if (ref!=null) { 
     ref.lock.lock(); 
     //there is no contention, unless a thread is already working on this entry 
     try { 
     if (map.containsKey(key)) { 
      value=ref.value; 
      break;  
     } else { 
      /*key was removed between get and lock*/ 
     } 
     } finally {ref.lock.unlock();} 
    } else value=null; 
} 

甲發燒友的方法將是重寫ConcurrentHashMap和有一個版本的putIfAbsent接受一個Runnable(如果該元件置於其上執行)。但是這遠遠更復雜。

基本上,ConcurrentHashMap實現了鎖定段,它位於每個條目的一個鎖定和整個地圖的一個全局鎖定之間。

+0

爲什麼我不能只使用'if(map.putIfAbsent(key,ref)== null)doSomethingElse();'?我認爲它仍然沒問題,因爲只有成功執行'putIfAbsent'的線程才能執行'doSomethingElse'方法。換句話說,使其成爲原子。 – adarshr 2013-03-19 14:05:46

+0

因爲'doSomethingElse'不會是原子的,而另一個線程在完成其他操作之前(即在'put'之後立即看到中間狀態)可以'獲得(鍵)'。 – Javier 2013-03-19 14:08:52

+0

是的,就是這一點。另一個線程不能調用'doSomethingElse',除非它是'putIfAbsent(key)'中成功的那個線程。請糾正我,如果我失去了一些東西。 – adarshr 2013-03-19 14:11:37