内部でConcurrentHashMap.compute()
は、共有メモリにある long 値をインクリメントおよびデクリメントします。読み取り、インクリメント/デクリメントはcompute
、同じキーのメソッド内でのみ実行されます。そのため、long 値へのアクセスは ConcurrentHashMap セグメントをロックすることによって同期されるため、インクリメント/デクリメントはアトミックです。私の質問は: マップ上のこの同期は、長い値の可視性を保証しますか? Map の内部同期に依存できますか、それとも long 値にする必要があります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:私が取り組んでいるコードに近い別の例を追加します。誰もオブジェクトを使用していないときに、マップ エントリを削除したいと考えています。long 値の読み取りと書き込みは、次の再マッピング関数内でのみ行われることに注意してください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;
}
}
}