高吞吐量是否在Java 8并发性中写入变量?

时间:2019-02-03 16:20:00

标签: concurrency java-8 locking java.util.concurrent

如果我在Java 8程序中有一个简单的Integer,它可以被多个线程读取和写入。

如果我被告知该应用程序需要支持高吞吐量读取和很少写入操作-答案很简单,我只是使用读写锁定。然后,多个线程可以并发执行读取而没有阻塞-并且阻塞仅在不经常执行写操作时发生。

但是,如果我被告知该应用程序需要支持高吞吐量写操作(即,共享变量正在由不同线程频繁更新)。无论我在这里使用哪种类型的锁,据我所知,它将始终导致线程阻塞-因为当线程获得该变量的锁并对其进行更新时,其余仍在尝试更新的线程变量将只需要等到他们获得锁之后-这是正确的还是我在Java 8中缺少某些内容?

我可以开始在共享变量上编写某种异步更新方法,其中线程调用它立即返回的update方法,而我在幕后使用某种数据结构将对共享的写操作排队变量。至少以这种方式,我将防止尝试更新共享变量时线程阻塞。授予这种方法将引发其他问题,例如线程应假定其保证了写入定义。成功,还是应该提供回叫通知更新成功等。除了类似的东西,当在Java 8中使用任何Lock进行高吞吐量写入时,我看不到阻塞的任何方法吗? (或者即使在高吞吐量写入的情况下,我还是应该接受阻塞并只使用Lock)。谢谢

1 个答案:

答案 0 :(得分:3)

严格来说,Integer –您可以使用LongAdder,它的实现恰好适合您的情况。如果您在这里,还有一些其他详细信息。

它在幕后使用CAS(比较和交换),与AtomicLong类似,但有一些区别。首先,它所持有的实际long value被包装在所谓的Cell中-基本上是一个类,可以将cas(比较并交换)value到新价值,很像二传手。该Cell也带有@sun.misc.Contended注释,以防止错误共享。这是它的解释(来自代码注释):

  

但是,驻留在数组中的原子对象往往会彼此相邻放置,因此在没有这种预防措施的情况下,大多数情况下它们会共享缓存行(对性能造成巨大的负面影响)。

这里的实现非常有趣。让我们看看调用add(long 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);
    }
}

这个想法是,如果Cell [] cs 为空,则之前没有争用,这意味着long value尚未初始化或所有先前的CAS操作都有被所有线程成功。在这种情况下,请尝试将新值CAS long value-如果可行,我们就完成了。但是,如果失败了,则会创建一个Cell []数组,以便每个单独的线程都尝试在其自己的空间中工作,从而最大程度地减少竞争。

如果我正确地理解了您的问题(这是我的,完全不是来自代码注释),那么下句话就是您真正关心的:

  

用简单的话来说:如果线程之间没有争用,则工作就好像使用了AtomicLong一样(否则),否则尝试为每个要处理的线程创建一个单独的空间。

如果您关心一些其他细节,我会发现有趣的事情:

Cell[]始终是2的幂(非常类似于HashMap内部数组);然后每个线程使用ThreadLocalRandom创建一些hashCode来尝试在数组Cell [] cs中查找要写入的条目,甚至使用Marsaglia XorShif再次进行哈希处理以尝试找到可用的插槽在这个数组中;阵列的大小以您拥有的核心数为上限(实际为2),可以调整该阵列的大小,使其可以扩展,并且所有这些操作都可以使用volatile int cellsBusy自旋锁来完成。这段代码很棒,但是正如我所说,我还没有全部。