import java.util.HashMap;
import java.util.Map;
public class MainTest
{
static Map<String, Integer> map = new HashMap();
public static void incr()
{
map.merge("counter", 1, Integer::sum);
}
public static void decr()
{
map.merge("counter", -1, Integer::sum);
}
public static void main(String[] args) throws Exception
{
map.put("counter", 0);
for (int i = 0; i < 10000; i++)
{
Thread t1 = new Thread(new Runnable()
{
@Override
public void run()
{
incr();
}
});
t1.join();
t1.start();
Thread t2 = new Thread(new Runnable()
{
@Override
public void run()
{
decr();
}
});
t2.join();
t2.start();
}
System.out.println(map);
}
}
当 main 方法运行时,结果是 {counter=-2}
。
为什么不是0?
最佳答案
Map
接口(interface)上merge
的Javadoc 说:
The default implementation makes no guarantees about synchronization or atomicity properties of this method. Any implementation providing atomicity guarantees must override this method and document its concurrency properties.
虽然 HashMap
覆盖了默认实现,但它没有关于该实现的并发属性的文档,但它确实有以下一般声明:
Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally
因此它不是线程安全的。
P.S.,不清楚为什么在启动相应线程之前调用 t1.join()
和 t2.join()
。
如果你撤销调用
t1.join();
t1.start();
到
t1.start();
t1.join();
和
t2.join();
t2.start();
到
t2.start();
t2.join();
你会得到 0 的输出。当然,如果你这样做,根本不会有并发修改,因为每个线程都会在前一个线程结束后启动。
另一种方法是在外部同步 map.merge
调用:
public static void incr()
{
synchronized(map) {map.merge("counter", 1, Integer::sum);}
}
public static void decr()
{
synchronized(map) {map.merge("counter", -1, Integer::sum);}
}
关于java 8 Map.merge 是线程安全的吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47647664/