在ConcurrentHashMap.compute()
I 内递增和递减位于共享内存中的一些 long 值。读取、递增/递减仅在compute
同一键的方法内执行。因此,通过锁定 ConcurrentHashMap 段来同步对 long 值的访问,因此递增/递减是原子的。我的问题是:地图上的这种同步是否能保证长期价值的可见性?我可以依靠 Map 的内部同步还是应该做多volatile
?
我知道,当您明确同步锁定时,可以保证可见性。ConcurrentHashMap
但是我对内部结构没有完全的了解。或者也许我今天可以信任它,但明天ConcurrentHashMap
的内部结构可能会发生某种变化:独占访问权将被保留,但可见性将消失……这是使我的长期价值不稳定的论据。
下面我将发布一个简化的示例。根据测试,今天没有比赛条件。volatile
但是我可以在没有for的情况下长期信任这个代码long value
吗?
class LongHolder {
private final ConcurrentMap<Object, Object> syncMap = new ConcurrentHashMap<>();
private long value = 0;
public void increment() {
syncMap.compute("1", (k, v) -> {
if (++value == 2000000) {
System.out.println("Expected final state. If this gets printed, this simple test did not detect visibility problem");
}
return null;
});
}
}
class IncrementRunnable implements Runnable {
private final LongHolder longHolder;
IncrementRunnable(LongHolder longHolder) {
this.longHolder = longHolder;
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
longHolder.increment();
}
}
}
public class ConcurrentMapExample {
public static void main(String[] args) throws InterruptedException {
LongHolder longholder = new LongHolder();
Thread t1 = new Thread(new IncrementRunnable(longholder));
Thread t2 = new Thread(new IncrementRunnable(longholder));
t1.start();
t2.start();
}
}
UPD:添加另一个更接近我正在处理的代码的示例。当没有其他人使用该对象时,我想删除地图条目。请注意,长值的读取和写入仅发生在重新映射函数内部ConcurrentHashMap.compute
:
public class ObjectProvider {
private final ConcurrentMap<Long, CountingObject> map = new ConcurrentHashMap<>();
public CountingObject takeObjectForId(Long id) {
return map.compute(id, (k, v) -> {
CountingObject returnLock;
returnLock = v == null ? new CountingObject() : v;
returnLock.incrementUsages();
return returnLock;
});
}
public void releaseObjectForId(Long id, CountingObject o) {
map.compute(id, (k, v) -> o.decrementUsages() == 0 ? null : o);
}
}
class CountingObject {
private int usages;
public void incrementUsages() {
--usages;
}
public int decrementUsages() {
return --usages;
}
}
UPD2:我承认我之前没有提供最简单的代码示例,现在发布一个真实的代码:
public class LockerUtility<T> {
private final ConcurrentMap<T, CountingLock> locks = new ConcurrentHashMap<>();
public void executeLocked(T entityId, Runnable synchronizedCode) {
CountingLock lock = synchronizedTakeEntityLock(entityId);
try {
lock.lock();
try {
synchronizedCode.run();
} finally {
lock.unlock();
}
} finally {
synchronizedReturnEntityLock(entityId, lock);
}
}
private CountingLock synchronizedTakeEntityLock(T id) {
return locks.compute(id, (k, l) -> {
CountingLock returnLock;
returnLock = l == null ? new CountingLock() : l;
returnLock.takeForUsage();
return returnLock;
});
}
private void synchronizedReturnEntityLock(T lockId, CountingLock lock) {
locks.compute(lockId, (i, v) -> lock.returnBack() == 0 ? null : lock);
}
private static class CountingLock extends ReentrantLock {
private volatile long usages = 0;
public void takeForUsage() {
usages++;
}
public long returnBack() {
return --usages;
}
}
}