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



最后通读了优秀的Concurrency In Practice一书,我遇到了BaseBoundedBuffer的清单 14.2。按原样,put 和 take 方法将允许计数超过缓冲区容量或低于 0。我知道该类是抽象的,但这是默认行为似乎很奇怪。是否有一些很好的理由说明为什么没有一些逻辑不允许计数超过容量或低于 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;
}
}

鉴于书中的示例子类,它旨在让儿童类负责在放置之前检查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章描述了一种由子类处理异常情况的方法。本章描述了实现此类功能的两种"痛苦"方法(抛出异常或sleep线程(,然后提供了一种更健壮的方法 - 使用条件队列(参见第 14.1.3 章(。

我想强调的是,您提到的代码示例不是复制和粘贴的实现,它只是切中要害的手段。

最新更新