【发布时间】:2014-05-23 17:15:05
【问题描述】:
我了解到新的Java(8)引入了新的同步工具如LongAccumulator(在atomic包下)。
在文档中说,当来自多个线程的变量更新频繁时,LongAccumulator 效率更高。
我想知道它是如何实现更高效的?
【问题讨论】:
标签: java concurrency atomic java-8
我了解到新的Java(8)引入了新的同步工具如LongAccumulator(在atomic包下)。
在文档中说,当来自多个线程的变量更新频繁时,LongAccumulator 效率更高。
我想知道它是如何实现更高效的?
【问题讨论】:
标签: java concurrency atomic java-8
这是一个非常好的问题,因为它展示了使用共享内存进行并发编程的一个非常重要的特征。在详细介绍之前,我必须退后一步。看看下面的类:
class Accumulator {
private final AtomicLong value = new AtomicLong(0);
public void accumulate(long value) {
this.value.addAndGet(value);
}
public long get() {
return this.value.get();
}
}
如果你创建这个类的一个实例并在一个循环中从一个线程调用方法accumulate(1),那么执行会非常快。但是,如果您从 两个线程 对同一实例调用该方法,则执行速度将慢大约 两个数量级。
您必须查看内存架构才能了解会发生什么。现在大多数系统都有non-uniform memory access。特别是,每个内核都有自己的 L1 高速缓存,它通常被结构化为具有 64 个八位字节的高速缓存行。如果一个核心对内存位置执行原子增量操作,它首先必须获得对相应缓存行的独占访问权限。如果它还没有独占访问权限,那是很昂贵的,因为需要与所有其他内核进行协调。
有一个简单且违反直觉的技巧可以解决这个问题。看看下面的类:
class Accumulator {
private final AtomicLong[] values = {
new AtomicLong(0),
new AtomicLong(0),
new AtomicLong(0),
new AtomicLong(0),
};
public void accumulate(long value) {
int index = getMagicValue();
this.values[index % values.length].addAndGet(value);
}
public long get() {
long result = 0;
for (AtomicLong value : values) {
result += value.get();
}
return result;
}
}
乍一看,由于额外的操作,这个类似乎更昂贵。但是,它可能比第一类快几倍,因为它执行内核已经独占访问所需缓存行的可能性更高。
要真正快速完成,您必须考虑更多事项:
long[8 * 4],并且只使用索引 0、8、16 和 24。综上所述,LongAccumulator对于某些用例来说效率更高,因为它使用冗余内存进行频繁使用的写操作,为了减少必须交换缓存行的次数核心之间。另一方面,读取操作的成本稍高一些,因为它们必须创建一致的结果。
【讨论】: