In my work, I find guys often make a mistaken of missed signals
thread_1 test condition and find it true, before it enters synchronize block to wait, thread_2 change the condition and sends out notification. CPU cycle come back for thread_1, thread_1 wait for a missed signal. May be a long time potentially forever.
If thread_1 is a executing service thread (engine thread) to process task, then it may be in danger of task accumulating, the whole system is blocked.
A similar error is invalid clean up
If thread_2 execute before thread_1, then the 'target' object would be never removed and exist in container forever. If the target is limited resources such as thread, connection pool, socket... Then it has dangerous all of limited resources are consumed and exhausted.
Java Concurrency in Practice chapter 14 Building Custom Synchronizers
The Condition Predicate
The condition predicate is the precondition that makes an operation state-dependent in the first place. In a bounded buffer, take can proceed only if the buffer is not empty; otherwise it must wait. For take, the condition predicate is "the buffer is not empty", which take must test for before proceeding. Similarly, the condition predicate for put is "the buffer is not full". Condition predicates are expressions constructed from the state variables of the class; BaseBoundedBuffer tests for "buffer not empty" by comparing count to zero, and tests for "buffer not full" by comparing countto the buffer size.
Wait - Waking Up Too Soon
When control re-enters the code calling wait, it has reacquired the lock associated with the condition queue. Is the condition predicate now true? Maybe. It might have been true at the time the notifying thread called notifyAll, but could have become false again by the time you reacquire the lock. Other threads may have acquired the lock and changed the object's state between when your thread was awakened and when wait reacquired the lock. Or maybe it hasn't been true at all since you called wait.
When using condition waits (Object.wait or Condition.await):
-
Always have a condition predicatesome test of object state that must hold before proceeding;
-
Always test the condition predicate before calling wait, and again after returning from wait;
-
Always call wait in a loop;
-
Ensure that the state variables making up the condition predicate are guarded by the lock associated with the condition queue;
-
Hold the lock associated with the the condition queue when calling wait,notify, or notifyAll; and
-
Do not release the lock after checking the condition predicate but before acting on it.
Wait - Missed Signals
A missed signal occurs when a thread must wait for a specific condition that is already true, but fails to check the condition predicate before waiting. Now the thread is waiting to be notified of an event that has already occurred.
Listing 14.7. Canonical Form for State-dependent Methods.
Notification
Whenever you wait on a condition, make sure that someone will perform a notification whenever the condition predicate becomes true.
Because multiple threads could be waiting on the same condition queue for different condition predicates, using notify instead of notifyAll can be dangerous, primarily because single notification is prone to a problem akin to missed signals.
Single notify can be used instead of notifyAll only when both of the following conditions hold:
- Uniform waiters. Only one condition predicate is associated with the condition queue, and each thread executes the same logic upon returning from wait; and
- One-in, one-out. A notification on the condition variable enables at most one thread to proceed.
The notification done by put and take in BoundedBuffer is conservative: a notification is performed every time an object is put into or removed from the buffer. This could be optimized by observing that a thread can be released from a wait only if the buffer goes from empty to not empty or from full to not full, and notifying only if a put or take one of these state transitions. This is called conditional notification
Listing 14.1. Structure of Blocking State-dependent Actions.
Propagating Precondition Failure to Callers
Polling and Sleeping
Condition Queues