问题是你无法控制notifyCalled.acquire()中哪个线程成功 .
例如,考虑这种情况:
线程A等待获取
线程B执行put两次并填充缓冲区 . 只有一个线程在等待,所以它调用notifyCalled.release()
线程C执行put并且由于缓冲区已满,它进入等待块 .
3中的notifyCalled.release()导致notifyCalled.aquire()在线程C而不是线程A中成功
由于缓冲区仍然是完整的,因此线程C(以及所有其他put操作)进入重新进入while循环并再次等待,并且线程B将永远不会收到它正在等待的释放 .
SOLUTION
当由于缓冲区已满(或与get操作相反)而未使用在放置操作块中获得的许可时,会出现问题 . 为避免这种情况,可以使用标志释放许可证,以便具有相反操作的另一个等待线程可以尝试获取它 .
此外,正如Daniel Pryden所述 inc() 也应该在monitorSemaphore锁内移动以避免竞争条件 .
请注意,这使得用于修改 blocksWaitingCount 的同步块变得不必要(尽管由于它们是无竞争的,因此它们对性能的影响很小) .
这是修改后的代码
public class BufferNonSync {
private int[] buffer = new int[] { 0};
private int start = 0;
private int last = 0;
private final int size = 1;
private int numberInBuffer = 0;
// Monitor variables
private Semaphore monitorSemaphore = new Semaphore(1);
private Semaphore notifyCalled = new Semaphore(0);
private int blocksWaitingCount = 0;
public void put(int input, int id) throws InterruptedException {
monitorSemaphore.acquire();
boolean acquired = false;
while (numberInBuffer == size) {
// Equivalent of wait()
if (acquired) {
dec();
notifyCalled.release();
}
inc();
monitorSemaphore.release();
notifyCalled.acquire();
monitorSemaphore.acquire();
acquired = true;
}
// Critical section
buffer[last] = input;
last = (last + 1) % size;
numberInBuffer++;
// Equivalent of notifyAll()
for (int i = val(); i > 0; i--) {
dec();
notifyCalled.release();
}
monitorSemaphore.release();
}
public int get(int id) throws InterruptedException {
monitorSemaphore.acquire();
boolean acquired = false;
while (numberInBuffer == 0) {
// Equivalent of wait()
if (acquired) {
dec();
notifyCalled.release();
}
inc();
monitorSemaphore.release();
notifyCalled.acquire();
monitorSemaphore.acquire();
acquired = true;
}
// Critical section
int temp = buffer[start];
start = (start + 1) % size;
numberInBuffer--;
// Equivalent of notifyAll()
for (int i = val(); i > 0; i--) {
dec();
notifyCalled.release();
}
monitorSemaphore.release();
return temp;
}
private void inc() {
blocksWaitingCount++;
}
private void dec() {
blocksWaitingCount--;
}
private int val() {
return blocksWaitingCount;
}
}