我有一个由多个线程访问的键值映射:
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()
方法(它调用get()
的{{1}}方法):
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;
}
}
方法(它调用上面的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);
}
}
)是否必要且正确?
锁实现提供比更广泛的锁定操作 可以使用同步方法和语句获得。
那么在我的代码中用synchronized (locks[hash % locks.length])
替换synchronization
是否可行?
编辑:如果您使用的是Java-8,请不要犹豫,参考@nosid的答案。
答案 0 :(得分: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);
}
}
在低争用下它可能会有更好的表现。
关于你的问题:
Striped
Lock
此处答案 1 :(得分:4)
如果您使用的是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);
}