如何解决ConcurrentHashMap无法按预期运行
我正在为电子选举投票,我的最初版本只有一个政党。每个选民将有不同的线程,这些线程将更新给定政党的投票计数。
我决定使用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",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吗?
解决方法
嗯,这里有一个复合动作。您获得给定键的映射值,将其递增一个,然后将其放回映射中的同一键。您必须保证所有这些语句都是原子执行的。但是给定的实现并不强加此先决条件。因此,您最终会遇到安全故障。
要解决此问题,您可以使用merge
中定义的原子ConcurrentHashMap
操作。整个方法调用是原子执行的。看起来就是这样。
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",Integer::sum);
Thread.sleep(5000); // Waits for the threads to finish
for (String s : voting.keySet())
System.out.println(s + ": " + voting.get(s));
运行此程序将产生以下输出:
,Gerb:116
假设有两个或多个线程执行
voting.put("GERB",voting.getOrDefault("GERB",0) + 1);
会发生什么? 假设键“ GERB”上的值现在等于10
- 线程#1获得值
voting.getOrDefault("GERB",0)
。是10 - 线程#2获得值
voting.getOrDefault("GERB",0)
。是10 - 线程#1加1,现在是11
- 线程#2加1,现在是11
- 线程#1将值11写回到
voting
- 线程#2将值11写回到
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",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",0) + 1);
分为三个步骤:
int temp=voting.getOrDefault("GERB",0); //1
temp++; //2
voting.put("GERB",temp); //3
现在在第1行和第3行之间,其他线程可以更改与“ GERB”关联的值,因为该方法已返回,没有什么可以阻止其他线程对其进行更改。因此,当您调用voting.put("GERB",temp)
时,您将覆盖它们的值,这会使它们的更新丢失。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。