Java 如何实现LongAccounter,使其更高效?

Java 如何实现LongAccounter,使其更高效?,java,concurrency,atomic,java-8,Java,Concurrency,Atomic,Java 8,我知道新的Java(8)引入了新的同步工具,例如(在原子包下) 在文档中,它指出当来自多个线程的变量更新频繁时,longcummerator更有效 我想知道如何实现它以提高效率?通过此 它看起来像一个旋转锁。这是一个非常好的问题,因为它显示了共享内存并发编程的一个非常重要的特征。在讨论细节之前,我必须退后一步。看看下面的课程: class Accumulator { private final AtomicLong value = new AtomicLong(0); publ

我知道新的Java(8)引入了新的同步工具,例如(在原子包下)

在文档中,它指出当来自多个线程的变量更新频繁时,longcummerator更有效

我想知道如何实现它以提高效率?

通过此


它看起来像一个旋转锁。

这是一个非常好的问题,因为它显示了共享内存并发编程的一个非常重要的特征。在讨论细节之前,我必须退后一步。看看下面的课程:

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();
    }
}
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;
    }
}
如果创建此类的一个实例,并在循环中从一个线程调用方法
accumulate(1)
,那么执行速度将非常快。但是,如果从两个线程对同一实例调用该方法,执行速度将慢两个数量级

您必须查看内存体系结构以了解发生了什么。现在的大多数系统都有一个。特别是,每个核心都有自己的一级缓存,通常被构造成具有64个八位字节的缓存线。如果内核在内存位置上执行原子增量操作,它首先必须以独占方式访问相应的缓存线。由于需要与所有其他核心进行协调,如果它还没有独占访问权,那么这将非常昂贵

有一个简单而反直觉的技巧可以解决这个问题。看看下面的课程:

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();
    }
}
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;
    }
}
乍一看,由于额外的操作,这个类似乎更昂贵。然而,它可能比第一个类快好几倍,因为它有更高的概率,执行的核心已经拥有对所需缓存线的独占访问权

<> P>为了使这个非常快,你必须考虑更多的事情:

  • 不同的原子计数器应位于不同的缓存线上。否则,您将用另一个问题替换一个问题,即。在Java中,您可以使用
    长[8*4]
    ,并且只使用索引
    0
    8
    16
    24
  • 必须明智地选择计数器的数量。如果不同的计数器太少,则缓存开关仍然太多。如果计数器太多,则会浪费L1缓存中的空间
  • getMagicValue方法应返回一个与核心id具有亲缘关系的值

总之,LongAccumulator对于某些用例更有效,因为它使用冗余内存进行频繁使用的写操作,以减少内核之间必须交换缓存线的次数。另一方面,读取操作的成本稍高一些,因为它们必须创建一致的结果。

这非常有趣。谢谢