如果我在Java 8程序中有一个简单的Integer,它可以被多个线程读取和写入。
如果我被告知该应用程序需要支持高吞吐量读取和很少写入操作-答案很简单,我只是使用读写锁定。然后,多个线程可以并发执行读取而没有阻塞-并且阻塞仅在不经常执行写操作时发生。
但是,如果我被告知该应用程序需要支持高吞吐量写操作(即,共享变量正在由不同线程频繁更新)。无论我在这里使用哪种类型的锁,据我所知,它将始终导致线程阻塞-因为当线程获得该变量的锁并对其进行更新时,其余仍在尝试更新的线程变量将只需要等到他们获得锁之后-这是正确的还是我在Java 8中缺少某些内容?
我可以开始在共享变量上编写某种异步更新方法,其中线程调用它立即返回的update方法,而我在幕后使用某种数据结构将对共享的写操作排队变量。至少以这种方式,我将防止尝试更新共享变量时线程阻塞。授予这种方法将引发其他问题,例如线程应假定其保证了写入定义。成功,还是应该提供回叫通知更新成功等。除了类似的东西,当在Java 8中使用任何Lock进行高吞吐量写入时,我看不到阻塞的任何方法吗? (或者即使在高吞吐量写入的情况下,我还是应该接受阻塞并只使用Lock)。谢谢
答案 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
自旋锁来完成。这段代码很棒,但是正如我所说,我还没有全部。