急切地使用 Condition#signal 是最佳实践吗?
Is it best-practise to Condition#signal eagerly?
Condition
JavaDoc 有以下代码示例:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
如果我实现了 BoundedBuffer#take
,我只会在 count == (items.length - 2)
时才调用 notFull.signal()
,以避免向其他线程发送信号,除非必要。我还注意到 ArrayBlockingQueue#removeAt
急切地调用 notFull.signal();
.
问题: 我的支票会引入错误吗?当条件为真时急切发出信号是最佳实践 Java 并发编程吗?我认为它会降低死锁的风险。它对性能有影响吗?
我的检查会引入错误吗?
是的。
条件 takeptr == (items.length - 1) 与填充底层缓冲区无关。看来缓冲区是以"cycling"的方式实现的。
在示例代码中,每次成功获取都会使缓冲区准备好进行放置,因此您应该每次都发出信号。如果不是,线程将等待 "put",尽管缓冲区中有空闲 space...
当条件为真时急切发出信号是最佳实践 Java 并发编程吗?
据我所知,在每个可能使条件更改为真的事件发生后,都有一个一般的经验法则来发出信号。但是适当的条件检查通常是通过等待线程来完成的。
您的示例场景非常简单(调用 "signal" 的代码确保 "notFull" 条件将被等待该 "notFull" 条件的任何线程满足,但通常也是 signalAll使用 signal 而不是 signal,因为 signal 方法只向一个线程发出信号(实际上可能不满足条件),这可能会导致死锁。
是的。您的检查引入了错误。用一个病态的例子很容易说明。
让我们假设 count = items.length
:
Thread1: put(o1); // count == items.length. Waiting on notFull.
Thread2: put(o2); // Waiting on notFull.
Thread3: put(o3); // Waiting on notFull.
Thread4: take(); // count -> items.length - 1
// There's space in the buffer, but we never signalled notFull.
// Thread1, Thread2, Thread3 will still be waiting to put.
take(); // count -> items.length - 2, signal notFull!
// But what happens if Thread4 manages to keep the lock?
take(); // count -> items.length - 3
...
take(); // count -> 0
Thread1: // Wakes up from Thread4's signal.
put(o1); // count -> 1
Thread2: put(o2); // Never signalled, still waiting on notFull.
Thread3: put(o3); // Never signalled, still waiting on notFull.
这可以使用 signalAll
来缓解,但您仍然会遇到一些问题:
- 因为即使只有一个 space 打开,您也会唤醒每个线程,所以会有更多的锁争用。
- 当您的缓冲区中只有一个 space 打开时,您仍然会有线程等待
put
。根据 implementation/documentation 我猜这个 可能 没问题,但在大多数情况下它仍然相当令人惊讶。
Condition
JavaDoc 有以下代码示例:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
如果我实现了 BoundedBuffer#take
,我只会在 count == (items.length - 2)
时才调用 notFull.signal()
,以避免向其他线程发送信号,除非必要。我还注意到 ArrayBlockingQueue#removeAt
急切地调用 notFull.signal();
.
问题: 我的支票会引入错误吗?当条件为真时急切发出信号是最佳实践 Java 并发编程吗?我认为它会降低死锁的风险。它对性能有影响吗?
我的检查会引入错误吗?
是的。 条件 takeptr == (items.length - 1) 与填充底层缓冲区无关。看来缓冲区是以"cycling"的方式实现的。
在示例代码中,每次成功获取都会使缓冲区准备好进行放置,因此您应该每次都发出信号。如果不是,线程将等待 "put",尽管缓冲区中有空闲 space...
当条件为真时急切发出信号是最佳实践 Java 并发编程吗?
据我所知,在每个可能使条件更改为真的事件发生后,都有一个一般的经验法则来发出信号。但是适当的条件检查通常是通过等待线程来完成的。
您的示例场景非常简单(调用 "signal" 的代码确保 "notFull" 条件将被等待该 "notFull" 条件的任何线程满足,但通常也是 signalAll使用 signal 而不是 signal,因为 signal 方法只向一个线程发出信号(实际上可能不满足条件),这可能会导致死锁。
是的。您的检查引入了错误。用一个病态的例子很容易说明。
让我们假设 count = items.length
:
Thread1: put(o1); // count == items.length. Waiting on notFull.
Thread2: put(o2); // Waiting on notFull.
Thread3: put(o3); // Waiting on notFull.
Thread4: take(); // count -> items.length - 1
// There's space in the buffer, but we never signalled notFull.
// Thread1, Thread2, Thread3 will still be waiting to put.
take(); // count -> items.length - 2, signal notFull!
// But what happens if Thread4 manages to keep the lock?
take(); // count -> items.length - 3
...
take(); // count -> 0
Thread1: // Wakes up from Thread4's signal.
put(o1); // count -> 1
Thread2: put(o2); // Never signalled, still waiting on notFull.
Thread3: put(o3); // Never signalled, still waiting on notFull.
这可以使用 signalAll
来缓解,但您仍然会遇到一些问题:
- 因为即使只有一个 space 打开,您也会唤醒每个线程,所以会有更多的锁争用。
- 当您的缓冲区中只有一个 space 打开时,您仍然会有线程等待
put
。根据 implementation/documentation 我猜这个 可能 没问题,但在大多数情况下它仍然相当令人惊讶。