我有一个必须使用的用例
我提供以下代码:
public void insertOrReplace(String key, String value) {
boolean updated = false;
do {
String oldValue = concurrentMap.get(key);
if (oldValue == null) {
oldValue = concurrentMap.putIfAbsent(key, value);
if (oldValue == null) {
updated = true;
}
}
if (oldValue != null) {
final String newValue = recalculateNewValue(oldValue, value);
updated = concurrentMap.replace(key, oldValue, newValue);
}
} while (!updated);
}
你认为它是正确的和线程安全的吗?
有更简单的方法吗?
答案 0 :(得分:7)
您可以使用下面的代码将其缩短一些,这相当于您的代码。我有一些压力测试它有成千上万的线程同时访问它:它按预期工作,执行了一些重试(循环)(显然,你无法证明在并发世界中测试的正确性。)
public void insertOrReplace(String key, String value) {
for (;;) {
String oldValue = concurrentMap.putIfAbsent(key, value);
if (oldValue == null)
return;
final String newValue = recalculateNewValue(oldValue, value);
if (concurrentMap.replace(key, oldValue, newValue))
return;
}
}
答案 1 :(得分:2)
您的方法似乎是线程安全的。如果您不需要ConcurrentHashMap的性能优势,请考虑使用常规HashMap并同步对它的所有访问。你的方法类似于AtomicInteger.getAndSet(int),所以应该没问题。我怀疑有一种更简单的方法可以做到这一点,除非你正在寻找一个图书馆电话来为你做这项工作。
答案 2 :(得分:2)
我认为这不正确。据我所知,merge()方法将是工作的正确工具。我目前遇到了同样的问题并写了一个小测试来查看结果。
这项测试启动了100名工人。它们中的每一个都是将地图中的值递增100次。所以预期的结果将是10000。
有两种类型的工人。使用替换算法的那个使用合并。测试使用不同的实现运行两次。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ConcurrentMapTest
{
private static ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
private final class ReplaceWorker implements Runnable
{
public void run()
{
for(int i = 0; i<100; i++)
{
Integer putIfAbsent = map.putIfAbsent("key", Integer.valueOf(1));
if(putIfAbsent == null)
return;
map.replace("key", putIfAbsent + 1);
}
}
}
private final class MergeWorker implements Runnable
{
public void run()
{
for(int i = 0; i<100; i++)
{
map.merge("key", Integer.valueOf(1), (ov, nv) -> {
return ov + 1;
});
}
}
}
public MergeWorker newMergeWorker()
{
return new MergeWorker();
}
public ReplaceWorker newReplaceWorker()
{
return new ReplaceWorker();
}
public static void main(String[] args)
{
map.put("key", 1);
ConcurrentMapTest test = new ConcurrentMapTest();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQu
for(int i = 0; i<100; i++)
{
threadPool.submit(test.newMergeWorker());
}
awaitTermination(threadPool);
System.out.println(test.map.get("key"));
map.put("key", 1);
threadPool = new ThreadPoolExecutor(10, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1000));
for(int i = 0; i<100; i++)
{
threadPool.submit(test.newReplaceWorker());
}
awaitTermination(threadPool);
System.out.println(test.map.get("key"));
}
private static void awaitTermination(ExecutorService threadPool)
{
try
{
threadPool.shutdown();
boolean awaitTermination = threadPool.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("terminted successfull: " + awaitTermination);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
result: terminted successfull: true 10000 terminted successfull: true 1743
问题在于你的案例中get和put之间存在差距,因此对于并发accsess地图结果会被覆盖。使用merge是一个原子操作,尽管文档没有说明任何内容。
答案 3 :(得分:1)
您可以使用MutableMapIterable.updateValueWith(K key, Function0<? extends V> factory, Function2<? super V,? super P,? extends V> function, P parameter)
中的Eclipse Collections。
如果地图中没有factory
参数,则function
参数会创建初始值。 parameter
参数将应用于地图值以及其他参数,以提供新的地图值。 updateValueWith()
作为function
的最终参数传递。即使在地图中没有键的情况下也会调用该函数。因此,初始值实际上是factory
应用于parameter
和function
的输出。 org.eclipse.collections.impl.map.mutable.ConcurrentHashMap
不得改变该值;它应该返回一个新值。在您的示例中,地图值是不可变的字符串,因此我们很好。
在像updateValueWith()
这样的ConcurrentMaps中,function
的实现也是线程安全的和原子的。重要的是recalculateNewValue()
不会改变映射值,或者它不是线程安全的。它应该返回新的值。在您的示例中,地图值是不可变的字符串,因此我们很好。
如果您的方法updateValueWith()
只进行字符串连接,请按照以下方式使用Function0<String> factory = () -> "initial ";
Function2<String, String, String> recalculateNewValue = String::concat;
MutableMap<String, String> map = new ConcurrentHashMap<>();
map.updateValueWith("test", factory, recalculateNewValue, "append1 ");
Assert.assertEquals("initial append1 ", map.get("test"));
map.updateValueWith("test", factory, recalculateNewValue, "append2");
Assert.assertEquals("initial append1 append2", map.get("test"));
。
ConcurrentMap<String, String> map = new ConcurrentHashMap<>();
map.compute("test", (key, oldValue) -> oldValue == null ? "initial append1 " : oldValue + "append1 ");
Assert.assertEquals("initial append1 ", map.get("test"));
map.compute("test", (key, oldValue) -> oldValue == null ? "initial append1 " : oldValue + "append2");
Assert.assertEquals("initial append1 append2", map.get("test"));
您可以使用Java 8的ConcurrentMap.compute(K key, BiFunction remappingFunction)来完成同样的事情,但它有一些缺点。
updateValueWith()
compute()
都会共享相同的lambdas,但每次调用{{1}}都会在堆上创建新的垃圾。 注意:我是Eclipse Collections的提交者