2

我有多個線程訪問的鍵值地圖:ConcurrentHashMap上的這種同步是否正確?

private final ConcurrentMap<Key, VersionValue> key_vval_map = new ConcurrentHashMap<Key, VersionValue>(); 

我定製get()put()方法遵循典型的check-then-act模式。因此,需要同步來確保原子性。爲了避免鎖定整個ConcurrentHashMap,我定義:

private final Object[] locks = new Object[10]; 
{ 
    for(int i = 0; i < locks.length; i++) 
     locks[i] = new Object(); 
} 

而且get()方法進入(它調用的ConcurrentHashMapget()法):

public VersionValue get(Key key) 
{ 
    final int hash = key.hashCode() & 0x7FFFFFFF; 

    synchronized (locks[hash % locks.length]) // I am not sure whether this synchronization is necessary. 
    { 
     VersionValue vval = this.key_vval_map.get(key); 
     if (vval == null) 
      return VersionValue.RESERVED_VERSIONVALUE; // RESERVED_VERSIONVALUE is defined elsewhere 
     return vval; 
    } 
} 

put()方法去(它調用上述get()方法):

public void put(Key key, VersionValue vval) 
{ 
    final int hash = key.hashCode() & 0x7FFFFFFF; 

    synchronized (locks[hash % locks.length]) // allowing concurrent writers 
    { 
     VersionValue current_vval = this.get(key); // call the get() method above 

     if (current_vval.compareTo(vval) < 0) // it is an newer VersionValue 
      this.key_vval_map.put(key, vval); 
    } 
} 

上述代碼有效。但是,正如你所知道的,在多線程編程中工作遠不是正確的。

我的問題是:

  1. 是必要的,正確的在我的代碼這種同步機制(尤其是synchronized (locks[hash % locks.length]))?

  2. Javadoc on Interface Lock,它說

鎖實現提供了比 可以使用同步方法和語句可獲得的更廣泛的鎖定操作。

然後是可行的在我的代碼Lock更換synchronization

編輯:如果您使用的是Java-8,請毫不猶豫地引用@nosid的答案。

回答

5

ConcurrentMap允許您使用樂觀鎖,而不是明確的同步:

VersionValue current_vval = null; 
VersionValue new_vval = null; 

do { 
    current_vval = key_vval_map.get(key); 

    VersionValue effectiveVval = current_vval == null ? VersionValue.RESERVED_VERSIONVALUE : current_vval; 

    if (effectiveVval.compareTo(vval) < 0) { 
     new_vval = vval; 
    } else { 
     break; 
    } 
} while (!replace(key, current_vval, new_vval)); 

... 

private boolean replace(Key key, VersionValue current, VersionValue newValue) { 
    if (current == null) { 
     return key_vval_map.putIfAbsent(key, newValue) == null; 
    } else { 
     return key_vval_map.replace(key, current, newValue); 
    } 
} 

這將可能有低的競爭下更好的性能。

關於你的問題:

  1. 如果使用番石榴,看看Striped
  2. 沒有,如果你使用的是Java你不需要的Lock附加功能在這裏
+0

'ConcurrentMap允許你使用樂觀鎖定而不是顯式同步':你可以給一些引用並且說一些關於'樂觀鎖定'的東西嗎?這超出了我的知識範圍。 – hengxin

+0

我想我已經理解了''樂觀鎖定'的聰明想法'while(!replace(key,current_vval,new_vval));'。而'Striped'也是我想要的。謝謝。 – hengxin

4

-8,您可以使用方法ConcurrentHashMap::merge而不是分兩步讀取和更新該值。

public VersionValue get(Key key) { 
    return key_vval_map.getOrDefault(key, VersionValue.RESERVED_VERSIONVALUE); 
} 

public void put(Key key, VersionValue vval) { 
    key_vval_map.merge(key, vval, 
     (lhs, rhs) -> lhs.compareTo(rhs) >= 0 ? lhs : rhs); 
}