ConcurrentHashMap上的这种同步是否正确?

hen*_*xin 2 java multithreading synchronization locking concurrenthashmap

我有一个由多个线程访问的键值映射:

private final ConcurrentMap<Key, VersionValue> key_vval_map = new ConcurrentHashMap<Key, VersionValue>();
Run Code Online (Sandbox Code Playgroud)

我的自定义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();
}
Run Code Online (Sandbox Code Playgroud)

get()方法去(它调用get()的方法ConcurrentHashMap):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码有效.但是,正如您所知,在多线程编程中工作远非正确.

我的问题是:

  1. 这种同步机制(特别是synchronized (locks[hash % locks.length]))在我的代码中是否必要且正确?

  2. 接口锁的Javadoc中,它说

锁实现提供了比使用同步方法和语句获得的更广泛的锁操作.

然后就是它可行,取代synchronization通过Lock在我的代码?

编辑:如果您使用的是Java-8,请不要犹豫,参考@nosid的答案.

axt*_*avt 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在低争用下它可能会有更好的性能.

关于你的问题:

  1. 如果您使用番石榴,请看一下 Striped
  2. 不,您不需要Lock此处的其他功能


nos*_*sid 5

如果您使用的是 Java-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);
}
Run Code Online (Sandbox Code Playgroud)