实践中的并发循环缓冲区错误?
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 章)。
我想强调一下,您提到的代码示例不是复制粘贴的实现,它只是切中要点。
终于读完了优秀的 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 章)。
我想强调一下,您提到的代码示例不是复制粘贴的实现,它只是切中要点。