您的解决方法实际上非常好。还有其他工具,您可以在其上构建一个有点类似的解决方案(例如,使用和逻辑删除值),但它们有自己的警告,我已经在略有不同的用例中使用了它们。computeIfPresent()
至于使用不实现映射值的类型,您可以在相应类型之上使用自己的包装器。这是将对象相等的自定义语义注入到 提供的原子替换/删除操作的最直接方法。equals()
ConcurrentMap
更新
下面是一个草图,显示了如何在 API 之上进行构建:ConcurrentMap.remove(Object key, Object value)
- 在用于值的可变类型之上定义包装器类型,同时定义基于当前可变值构建的自定义方法。
equals()
- 在(要传递到的 lambda)中,创建值的深层副本(该值的类型为新包装器类型),并执行逻辑,确定是否需要在副本上删除该值。
BiConsumer
forEach
- 如果需要删除该值,请调用 。
remove(myKey, myValueCopy)
- 如果在计算是否需要删除该值时发生了一些并发更改,则将返回(除非 ABA 问题,这是一个单独的主题)。
remove(myKey, myValueCopy)
false
下面是一些代码来说明这一点:
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
public class Playground {
private static class AtomicIntegerWrapper {
private final AtomicInteger value;
AtomicIntegerWrapper(int value) {
this.value = new AtomicInteger(value);
}
public void set(int value) {
this.value.set(value);
}
public int get() {
return this.value.get();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AtomicIntegerWrapper)) {
return false;
}
AtomicIntegerWrapper other = (AtomicIntegerWrapper) obj;
if (other.value.get() == this.value.get()) {
return true;
}
return false;
}
public static AtomicIntegerWrapper deepCopy(AtomicIntegerWrapper wrapper) {
int wrapped = wrapper.get();
return new AtomicIntegerWrapper(wrapped);
}
}
private static final ConcurrentMap<Integer, AtomicIntegerWrapper> MAP
= new ConcurrentHashMap<>();
private static final int NUM_THREADS = 3;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; ++i) {
MAP.put(i, new AtomicIntegerWrapper(1));
}
Thread.sleep(1);
for (int i = 0; i < NUM_THREADS; ++i) {
new Thread(() -> {
Random rnd = new Random();
while (!MAP.isEmpty()) {
MAP.forEach((key, value) -> {
AtomicIntegerWrapper elem = MAP.get(key);
if (elem == null) {
System.out.println("Oops...");
} else if (elem.get() == 1986) {
elem.set(1);
} else if ((rnd.nextInt() & 128) == 0) {
elem.set(1986);
}
});
}
}).start();
}
Thread.sleep(1);
new Thread(() -> {
Random rnd = new Random();
while (!MAP.isEmpty()) {
MAP.forEach((key, value) -> {
AtomicIntegerWrapper elem =
AtomicIntegerWrapper.deepCopy(MAP.get(key));
if (elem.get() == 1986) {
try {
Thread.sleep(10);
} catch (Exception e) {}
boolean replaced = MAP.remove(key, elem);
if (!replaced) {
System.out.println("Bailed out!");
} else {
System.out.println("Replaced!");
}
}
});
}
}).start();
}
}
您将看到“救助!”的打印输出,与“替换!”(删除成功,因为您关心没有并发更新),计算将在某个时刻停止。
- 如果移除自定义方法并继续使用副本,您将看到源源不断的“救助!”,因为副本永远不会被视为等于地图中的值。
equals()
- 如果不使用副本,则不会看到打印出来的“Bailed out!”,并且会遇到您正在解释的问题 - 无论并发更改如何,值都会被删除。