实践中的并发循环缓冲区错误?

Concurrency In Practice Circular Buffer Error?

终于读完了优秀的 Concurrency In Practice 一书,我看到了 BaseBoundedBuffer 的清单 14.2。实际上,put 和 take 方法将允许计数超过缓冲区容量或低于 0。我知道 class 是抽象的,但似乎很奇怪这是默认行为。为什么没有一些逻辑不允许计数超出容量或低于 0 是否有充分的理由?也许是这样的,

if(count != buf.length)
     ++count;
@ThreadSafe
public abstract class BaseBoundedBuffer<V> {
     @GuardedBy("this") private final V[] buf;
     @GuardedBy("this") private final int tail;
     @GuardedBy("this") private final int head;
     @GuardedBy("this") private final int count;

     protected BaseBoundedBuffer (int capacity) {
          this.buf = (V[]) new Object[capacity];
     }

     protected synchronized final void doPut(V v) {
          buf[tail] = v;
          if (++tail == buf.length)
               tail = 0;
          ++count;
     }

      protected synchronized final V doTake() {
          V v = buf[head];
          buf[head] = null;
          if (++head == buf.length)
               head = 0;
          --count;
          return v;
     }

     public synchronized final boolean isFull() {
          return count == buf.length;
     }

     public synchronized final boolean isEmpty() {
          return count == 0;
     }
}

书中给出的例子child class似乎是让child class负责检查isFull在放之前和 isEmpty 在拿之前。这样的实现,再去检查是浪费时间。

@ThreadSafe
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
    public GrumpyBoundedBuffer(int size) { super(size); }

    public synchronized void put(V v) throws BufferFullException {
        if (isFull())
            throw new BufferFullException();
        doPut(v);
    }

    public synchronized V take() throws BufferEmptyException {
        if (isEmpty())
            throw new BufferEmptyException();
        return doTake();
    }
}

在现实世界中,解释如何使用这些方法的适当 JavaDoc 对于避免您已确定的两个潜在错误至关重要。

不言而喻,仅仅因为某些东西在书中并不意味着它是正确的、最佳的,甚至是好的。你对实施持怀疑态度是对的。

我们不应该让 count 运行 越界,但是这个例子假设检查这个条件被传播到调用者。我们不能只抛出异常,因为在多线程程序中,可能会以非异常方式预期和处理这种行为(例如,等待条件满足)。我们也不能只说 if(count != buf.length) ++count;,因为这将是处理逻辑的一部分,并且可能与调用者或子类中实现的逻辑冲突。

此示例是大图的一部分 - 14.1.1. Example: propagating precondition failure to callers 章描述了一种方法,其中异常情况由子类处理。本章描述了两种 "painful" 实现此类功能的方法(抛出异常或 sleep 运行线程),然后提供了一种更健壮的方法 - 使用条件队列(请参阅第 14.1.3 章)。

我想强调一下,您提到的代码示例不是复制粘贴的实现,它只是切中要点。