[英]ConcurrentHashMap does not work as expected
我正在计算电子选举的选票,并且在我的初始版本中我只有一个政党。 每个选民将有不同的线程,线程将更新给定政党的选票计数。
我决定使用 ConcurrentHashMap,但结果不是我所期望的......
Map<String, Integer> voting = new ConcurrentHashMap<>();
for (int i = 0; i < 16; i++) {
new Thread(() -> {
voting.put("GERB", voting.getOrDefault("GERB", 0) + 1);
}).start();
}
for (int i = 0; i < 100; i++) {
voting.put("GERB", voting.getOrDefault("GERB", 0) + 1);
}
Thread.sleep(5000); // Waits for the threads to finish
for (String s : voting.keySet()) {
System.out.println(s + ": " + voting.get(s));
}
结果每次都不同 - 它的范围从 114 到 116。
ConcurrentHashMap 不应该同步吗?
嗯,这里有一个复合动作。 您获得给定键的映射值,将其递增 1,然后将其放回映射中的同一个键。 您必须保证所有这些语句都以原子方式执行。 但是给定的实现并没有强加这个先决条件。 因此,您最终会遇到安全故障。
要解决此问题,您可以使用ConcurrentHashMap
定义的原子merge
操作。 整个方法调用以原子方式执行。 这是它的外观。
Map<String, Integer> voting = new ConcurrentHashMap<>();
for (int i = 0; i < 16; i++)
new Thread(() -> {
voting.merge("GERB", 1, Integer::sum);
}).start();
for (int i = 0; i < 100; i++)
voting.merge("GERB", 1, Integer::sum);
Thread.sleep(5000); // Waits for the threads to finish
for (String s : voting.keySet())
System.out.println(s + ": " + voting.get(s));
运行此程序会产生以下输出:
日耳曼语:116
假设有两个或更多线程执行voting.put("GERB", voting.getOrDefault("GERB", 0) + 1);
发生什么了? 假设键“GERB”的值现在等于 10
voting.getOrDefault("GERB", 0)
。 它是 10voting.getOrDefault("GERB", 0)
。 它是 10voting
voting
现在,虽然完成了 2 个线程,但由于并发,该值仅增加了 1。
所以,是的, ConcurrentHashMap
方法是同步的。 这意味着,当一个线程执行例如put
,另一个线程会等待。 但是无论如何它们都不会同步外部线程。
如果您执行多个呼叫,则必须自行同步它们。 例如:
final Map<String, Integer> voting = new ConcurrentHashMap<>();
for (int i = 0; i < 16; i++) {
new Thread(() -> {
synchronized (voting) { // synchronize the whole operation over the same object
voting.put("GERB", voting.getOrDefault("GERB", 0) + 1);
}
}).start();
}
UPD正如评论中所指出的,请记住, voting
对象上的同步并不能保证与 ConcurentHahMap 方法本身的同步。 如果可以同时执行这些调用,则必须为每次调用voting
方法执行该同步。 事实上,您可以使用任何其他对象进行同步(不需要voting
):只需要所有线程都相同即可。
但是,因为它注意到@Holger,这违背了的根本目的ConcurentHashMap
。 要在不锁定线程的情况下利用ConcurentHashMap
的原子机制,如果值被另一个线程更改,您可以使用方法replace重试操作:
for (int i = 0; i < 16; i++) {
new Thread(() -> {
Integer oldValue, newValue;
do {
oldValue = voting.getOrDefault("GERB", 0);
newValue = oldValue + 1; // do some actions over the value
} while (!voting.replace("GERB", oldValue, newValue)); // repeat if the value was changed
}).start();
}
你可以把这条线分割成voting.put("GERB", voting.getOrDefault("GERB", 0) + 1);
分为三步:
int temp=voting.getOrDefault("GERB",0); //1
temp++; //2
voting.put("GERB",temp); //3
现在在第 1 行和第 3 行之间,其他线程可以更改与“GERB”关联的值,因为该方法已返回,没有什么可以阻止其他线程更改它。 因此,当您调用voting.put("GERB",temp)
,您会覆盖它们的值,这会使它们的更新丢失。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.