java 8 Map.merge是线程安全的吗? import java.util.HashMap; 导入java.util.Map; 公共类测试 { 静态映射映射=新的HashMap(); 公共静态无效增量() { 合并(“计数器”,1,整数::和); } 公共静态无效声明() { 合并(“计数器”,-1,整数::和); } 公共静态void main(字符串[]args)引发异常 { 地图放置(“计数器”,0); 对于(int i=0;i

java 8 Map.merge是线程安全的吗? import java.util.HashMap; 导入java.util.Map; 公共类测试 { 静态映射映射=新的HashMap(); 公共静态无效增量() { 合并(“计数器”,1,整数::和); } 公共静态无效声明() { 合并(“计数器”,-1,整数::和); } 公共静态void main(字符串[]args)引发异常 { 地图放置(“计数器”,0); 对于(int i=0;i,java,java-8,Java,Java 8,运行main方法时,结果是{counter=-2}。 为什么不是0?在Map界面上的merge的Javadoc说: 默认实现不保证此方法的同步性或原子性属性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性 虽然HashMap覆盖默认实现,但它没有关于该实现的并发属性的文档,但它有以下一般性语句: 请注意,此实现不同步。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了该映射,则必须在外部对其进行同步 因此它不是线程安全的 另外,不清楚为什么在启动相应线程之前调用t1.joi

运行main方法时,结果是
{counter=-2}

为什么不是0?

Map
界面上的
merge
的Javadoc说:

默认实现不保证此方法的同步性或原子性属性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性

虽然
HashMap
覆盖默认实现,但它没有关于该实现的并发属性的文档,但它有以下一般性语句:

请注意,此实现不同步。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改了该映射,则必须在外部对其进行同步

因此它不是线程安全的

另外,不清楚为什么在启动相应线程之前调用
t1.join()
t2.join()

如果你把电话倒过来

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);
    }

}

您将获得0的输出。当然,如果您这样做,将不会有任何并发修改,因为每个线程将在前一个线程死亡后启动

另一种方法是同步
map.merge
外部调用:

    t2.start();
    t2.join();

对于修改数据结构(如
HashMap
)的特定单一方法来说,要求线程安全性有点奇怪,因为它通常被证明是线程安全的

当您想要同时修改映射时,必须寻找支持并发更新的实现,这通常通过实现
ConcurrentMap
来显示。对于这些映射,即使是
default
实现也足够了,因为它是在其他接口方法之上实现的,这些接口方法保证是线程安全的。但是,如果您希望保证原子性,则需要一个实现,用适当的实现覆盖该方法,例如:

合并
…整个方法调用是以原子方式执行的。在计算过程中,其他线程在此映射上尝试的某些更新操作可能会被阻止,因此计算应该简短,并且不得尝试更新此映射的任何其他映射

要修复并简化示例,请执行以下操作:

public static void incr()
{
    synchronized(map) {map.merge("counter", 1, Integer::sum);}
}

public static void decr()
{
    synchronized(map) {map.merge("counter", -1, Integer::sum);}
}

这允许并发度高于2,但由于工作线程可能能够以循环调度新作业的速度执行此简单任务,因此产生的并发度可能仍然接近2。

否。为什么会这样
HashMap
不是线程安全的,因此,
HashMap
上的任何方法都不是线程安全的。
    t2.join();
    t2.start();
    t2.start();
    t2.join();
public static void incr()
{
    synchronized(map) {map.merge("counter", 1, Integer::sum);}
}

public static void decr()
{
    synchronized(map) {map.merge("counter", -1, Integer::sum);}
}
Map<String, Integer> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10000; i++) {
    Thread t1 = new Thread(() -> map.merge("counter", -1, Integer::sum));
    Thread t2 = new Thread(() -> map.merge("counter",  1, Integer::sum));
    t1.start();
    t2.start();
    t1.join();
    t2.join();
}
System.out.println(map);
ExecutorService threadPool
    = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
Map<String, Integer> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10000; i++) {
    threadPool.execute(() -> map.merge("counter", -1, Integer::sum));
    threadPool.execute(() -> map.merge("counter",  1, Integer::sum));
}
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.DAYS);
System.out.println(map);