我知道在computeIfAbsent岁左右有很多问题.

具体地说,我正在寻找的是理解关于并发散列映射的原子性的语句.

the JavaDoc

整个方法调用都是自动执行的,因此每个键最多应用一次函数.

如果两个线程try 使用不同的键执行computeIfAbsent,并且发现在这两种情况下映射都不包含它们,那么COMPUTE IF ASSIVE函数的结果执行会是并发的吗?我知道,如果两个线程都试图添加相同的键,它们将不会并发.

使用了原子一词,并且提到这意味着每个密钥至多应用一次.但并没有特别提到该方法上的同步行为.

顺便说一句,这与我有关,因为ComputeIfAbent调用的方法修改后在其主体中使用类的一个字段.*

我想了解两个不同键的ComputeIfAbent方法的两个不同线程执行是否会导致线程问题.

从本质上讲,我必须了解同步对字段变量的访问以及随后在我调用的culteIfAbent方法中使用它的过程.

*(调用的ComputeIfAbsend方法是唯一修改该字段的方法.除了来自散列映射ComputeIfAbent方法的调用之外,没有其他方法调用者.只有一个并发散列映射的实例调用了ComputeWithAbted方法,该方法调用了所讨论的"原子"方法)

我的场是不稳定的,以避免对原子可见性的潜在担忧.

推荐答案

在某些情况下,可以针对不同的键值并发执行映射函数,因此重要的是映射函数是线程安全的.

computeIfAbsent方法只保证映射函数不会同时调用相同的键值.还要注意,A Map通过将多个关键字散列到条目桶中来工作,并且如果computeIfAbsent(a, mapFunc)computeIfAbsent(b, mapFunc)同时被调用,并且具有映射到相同子表ConcurrentHashMap的关键字A+B对,则每个关键字的mapFunc将被一个接一个地运行,而不是同时运行.

然而,如果不同的键没有解析到ConcurrentHashMap以内的同一子表,您应该期望不同的线程为不同的键值同时调用您的映射函数.

下面的示例显示了检测并发调用方的线程安全映射函数:

public static void main(String[] args) throws InterruptedException {
    ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>(2096, 1.0f);
    AtomicInteger concurrent = new AtomicInteger();
    Function<String, String> mappingFunction = s -> {
        int c = concurrent.incrementAndGet();
        String value = "Value:"+s +" concurrent="+c+" thread="+Thread.currentThread().getName();
        if (c != 1)
            System.out.println("Multiple callers for "+value);
        try { Thread.sleep(50); } catch (InterruptedException ignore) { }
        concurrent.decrementAndGet();
        return value;
    };
    Runnable task = () -> {
        Random r = new Random();
        for (int i = 0; i < 10_000; i++)
            map.computeIfAbsent(String.valueOf(r.nextInt(10240)), mappingFunction);
    };

    Thread a = new Thread(task, "one");

    a.start();
    task.run();
    a.join();
    map.values().stream().limit(32).forEach(System.out::println);
}

如果运行足够长,则会出现mappingFunction内的计数器显示两个实例同时在这对线程上运行的情况.

EDIT

要回答你对synchronized (r)条的 comments :

请注意,computeIfAbsent内部有一个无限的while循环,它只在breakreturn上退出,mappingFunction.apply(key)在两个位置被调用:

  1. 当密钥是子表中的第一个条目时,它运行到synchronized (r)块.正如前面的Node<K,V> r = new ReservationNode<K,V>()行声明的那样,r上永远不会有来自不同线程的争用,但只有一个线程成功进入if (casTabAt(...)) { binCount = 1; ... } 块并返回,其他失败的线程继续循环.

  2. 当关键字不是子表中的第一个条目时,它运行到synchronized (f)块,这将阻止除一个线程之外的所有线程try computeIfAbsent以获取散列到相同子表的不同关键字.当每个线程进入该块时,它验证f是未改变的,并且如果是,则返回现有的或计算出的新值,否则继续循环.

Java相关问答推荐

具有额外列的Hibert多对多关系在添加关系时返回NonUniqueHealthExcellent

@从类文件中删除JsonProperty—Java

在Java中测试DAO方法:假实现与内存数据库

在AnyLogic中增加变量计数

JDK22执行repackage of goal org. springframework. boot:spring—boot—maven—plugin:3.2.3:repackage failed:unsupported class file major version 66—>

扩展到弹出窗口宽度的JavaFX文本字段

不推荐使用的Environment.getExternalStorageDirectory().getAbsolutePath()返回的值不同于新的getExternalFilesDir(空)?

PDFBox未加载内容

如何让JVM在SIGSEGV崩溃后快速退出?

在JDK 1.8源代码中,为什么使用A-B 0来确定哪个更大,而不是A B?

Docker不支持弹性APM服务器

如何在太阳系模拟器中添加月球?

如何用内置Java从JavaFX应用程序中生成.exe文件?

Java在操作多个属性和锁定锁对象时使用同步和易失性

没有Tomcat,IntelliJ如何在本地运行API?

循环不起作用只有第一个元素重复

为什么Spring要更改Java版本配置以及如何正确设置?

ExecutorService:如果我向Executor提交了太多任务,会发生什么?

为什么child-pom会创建一个新版本

如何在 Android Studio 中删除 ImageView 和屏幕/父级边缘之间的额外空间?