Concurrency Java 8并发中对变量的高吞吐量写入?

Concurrency Java 8并发中对变量的高吞吐量写入?,concurrency,java-8,locking,java.util.concurrent,Concurrency,Java 8,Locking,Java.util.concurrent,如果我在Java 8程序中有一个简单的整数,它可以被多个线程读写 如果有人告诉我,这个应用程序需要支持高吞吐量的读取和很少的写入,那么答案很简单,我只需要使用读写锁。然后,多个线程可以在没有阻塞的情况下并发执行读取,并且只有在不经常写入的情况下才会发生阻塞 但是在这种情况下,如果我被告知应用程序需要支持高吞吐量写入(即共享变量由不同线程频繁更新)。无论我在这里使用什么类型的锁,据我所知,它总是会导致线程阻塞——当一个线程获得变量的锁并对其进行更新时,其他试图更新变量的线程只需等待它们获得锁——这

如果我在Java 8程序中有一个简单的整数,它可以被多个线程读写

如果有人告诉我,这个应用程序需要支持高吞吐量的读取和很少的写入,那么答案很简单,我只需要使用读写锁。然后,多个线程可以在没有阻塞的情况下并发执行读取,并且只有在不经常写入的情况下才会发生阻塞

但是在这种情况下,如果我被告知应用程序需要支持高吞吐量写入(即共享变量由不同线程频繁更新)。无论我在这里使用什么类型的锁,据我所知,它总是会导致线程阻塞——当一个线程获得变量的锁并对其进行更新时,其他试图更新变量的线程只需等待它们获得锁——这是正确的还是我在Java 8中遗漏了什么


我可以开始在共享变量上编写某种异步更新方法,线程调用它立即返回的更新方法,然后在后台使用某种数据结构将对共享变量的写入排队。至少通过这种方式,我可以防止线程在尝试更新共享变量时阻塞。当然,这种方法会引发其他问题,比如线程是否假定其已被写入def。成功,或者我应该提供一个回调来通知更新成功等。除此之外,在使用Java 8中的任何锁进行高吞吐量写入时,我认为没有办法绕过阻塞?(或者我应该只接受阻塞,并使用锁,即使在高吞吐量写入的情况下也是如此)。感谢

严格地说是
整数
-您可以使用
长加法器
,它的实现似乎正适合您的情况。如果你在意的话,这里有一些额外的细节

它在引擎盖下使用
CAS
(比较和交换),与
AtomicLong
非常相似,但有一些区别。首先,它所持有的实际
长值
被包装在一个所谓的
单元格中
——基本上是一个类,允许
cas
(比较和交换)将
转换为一个新值,如果需要的话,很像一个setter。此
单元格
还带有
@sun.misc.completed
注释,以防止错误共享;下面是对它的解释(来自代码注释):

但驻留在阵列中的原子对象往往彼此相邻,因此在没有这种预防措施的情况下,通常会共享缓存线(对性能有巨大的负面影响)

这里的实现非常有趣。让我们看看调用
add(长x)
方法时会发生什么:

 public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
public void add(长x){
单元[]cs;长b,v;int m;单元c;
如果((cs=单元格)!=null | |!casBase(b=base,b+x)){
布尔无竞争=真;
如果(cs==null | |(m=cs.length-1)<0||
(c=cs[getProbe()&m])==null||
!(无竞争=c.cas(v=c.value,v+x)))
长累积(x,空,无竞争);
}
}
其思想是,如果
Cell[]cs
为空,则之前没有争用,这意味着
long value
要么未初始化,要么所有线程都已成功执行所有先前的
CAS
操作。在这种情况下,尝试将新值
CAS
转换为
long-value
——如果成功,我们就完成了。但如果失败了,则会创建一个
单元格[]
数组,这样每个单独的线程都会尝试在自己的空间中工作,从而最大限度地减少争用

如果我正确理解了您的问题,那么下一句话就是您真正关心的内容(它是我的,根本不是来自代码注释):

简单地说:如果线程之间没有争用,那么工作就像使用了
AtomicLong
一样进行(某种程度上),否则尝试为每个线程创建一个单独的空间

如果您关心一些我觉得有趣的其他细节:


Cell[]
总是二的幂(很像
HashMap
内部数组);然后,每个线程使用
ThreadLocalRandom
创建一些hashCode,以尝试在数组
Cell[]cs
中查找要写入的条目,或者甚至使用
Marsaglia XorShif
再次重新散列,以尝试在此数组中查找空闲插槽;数组的大小被限制为您拥有的核数(实际上最接近2的幂),该数组可以调整大小,因此它可以增长,所有这些操作都使用
volatile int cellsBusy
自旋锁完成。这段代码很棒,但正如我所说的,我并不完全理解

非常感谢您的回复。是的,你是对的,不知道为什么我以前错过了…长加法器更多的是用于维护计数和总和,但我可以使用它的变化-长累加器用于维护最小值和最大值。再次感谢。