【问题标题】:Is this synchronization on ConcurrentHashMap correct?ConcurrentHashMap 上的这种同步是否正确?
【发布时间】:2014-07-14 08:41:52
【问题描述】:

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

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 的答案。

【问题讨论】:

    标签: java multithreading synchronization locking concurrenthashmap


    【解决方案1】:

    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. 不,这里不需要Lock 的额外功能

    【讨论】:

    • ConcurrentMap allows you to use optimistic locking instead of explicit synchronization: 能否请您提供一些参考资料并多谈谈optimistic locking?这超出了我的知识范围。
    • 我想我已经理解了您使用while (!replace(key, current_vval, new_vval));optimistic locking 的聪明想法。而Striped 也是我想要的。谢谢。
    【解决方案2】:

    如果您使用的是 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);
    }
    

    【讨论】:

      猜你喜欢
      • 2014-04-24
      • 2010-11-04
      • 1970-01-01
      • 2014-08-07
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      • 1970-01-01
      • 2011-08-08
      相关资源
      最近更新 更多