无锁圆阵

时间:2014-01-02 19:42:26

标签: java lock-free circular-buffer

我正在考虑实现一个无锁的循环数组。一个问题是以无锁的方式保持头部和尾部指针。我想到的代码是:

int circularIncrementAndGet(AtomicInteger i) {
    i.compareAndSet(array.length - 1, -1);
    return i.incrementAndGet();
}

然后我会做类似的事情:

void add(double value) {
    int idx = circularIncrementAndGet(tail);
    array[idx] = value;
}

(注意,如果数组是完整的旧值将被覆盖,我很好)。

有没有人发现这个设计有问题?我怀疑可能存在一种我没有看到的竞争状态。

4 个答案:

答案 0 :(得分:4)

查看disruptor:http://lmax-exchange.github.io/disruptor/,它是Java中的一个开源无锁循环缓冲区。

答案 1 :(得分:4)

更简单的方法是使用2大小的幂并执行以下操作。

 final double[] array;
 final int sizeMask;
 final AtomicInteger i = new AtomicInteger();

 public CircularBuffer(int size) {
      assert size > 1 && ((size & (size -1)) == 0); // test power of 2.
      array = new double[size];
      sizeMask = size -1;
 }

 void add(double value) {
     array[i.getAndIncrement() & sizeMask] = value;
 }

答案 2 :(得分:2)

是的,有竞争条件。

i = array.length - 2,两个主题输入circularIncrementAndGet()

Thread 1: i.compareAndSet(array.length - 1, -1) results in i = array.length - 2
Thread 2: i.compareAndSet(array.length - 1, -1) results in i = array.length - 2
Thread 1: i.incrementAndGet() results in i = array.length - 1
Thread 2: i.incrementAndGet() results in i = array.length

当线程2到达ArrayIndexOutOfBoundsException时(以及随后array[idx] = value的所有后续调用,直到add()溢出),才会导致i

@Peter Lawrey提出的解决方案没有遇到这个问题。

答案 3 :(得分:1)

如果您坚持以下约束:

  • 任何时候只允许一个线程修改头部指针
  • 任何时候只允许一个线程修改尾指针
  • Dequeue-on-empty给出一个返回值,表示没有做任何事情
  • Enqueue-on-full给出一个返回值,表示没有做任何事情
  • 您不会计算队列中存储的值的数量。
  • 你'浪费'了一个永远不会被使用的数组中的索引,这样你就可以分辨出数组是满的还是空的而不必继续计算。

可以实现循环数组/队列。

排队线程拥有尾指针。出列线程拥有头指针。除了一个条件,这两个线程到目前为止不共享任何状态,因此没有问题。

这个条件是测试空虚或丰满。

考虑为空表示头==尾;考虑完全意味着tail == head - 1模数组大小。 Enqueue必须检查队列是否已满,dequeue必须检查队列是否为空。你需要在数组中浪费一个索引来检测full和empty之间的区别 - 如果你排入最后一个bucket,那么full将是head == tail而empty将是head == tail,现在你就陷入僵局 - 你认为你在同一时间是空虚和充实的,所以没有工作可以完成。

在执行这些检查时,可能会在比较时更新一个值。然而,由于这两个值是单调递增的,因此没有正确性问题:

  • 如果在dequeue方法中,head == tail在比较期间计算为true,但是后面的尾部向前移动,没有问题 - 你认为当它实际上没有时,数组是空的,但没什么大不了的,你只需从dequeue方法返回false,然后再试一次。
  • 如果在enqueue方法中,tail == head - 1计算为true,但在此之后,head会增加,那么你会认为数组已满,但实际上并非如此,但是重要的是,你只需从入队中返回假,然后再试一次。

这是我在Dobb博士多年前发现的实施背后所使用的设计,它对我很有帮助:

http://www.drdobbs.com/parallel/lock-free-queues/208801974