It may be a false awakening, but it is not the only possible reason. This is definitely a problem with your logic. You need to put the wait inside a loop that re-tests the condition.
When a thread wakes up from waiting, it no longer has a lock. He let go of the lock, when he began to wait, he needed to lock the lock again before he could continue. Just an awakened thread, as a rule, may be the next in the line due to the proximity of the thread (which is probably why your code works most of the time), but there is still a chance that this is not so; another thread can enter and lock the lock, do its thing and leave nextVal null before the awakened thread can take the lock. This means that checking for zero, that the thread done before the wait is no longer relevant. You should go back and test again as soon as you have a lock.
Change the code to use a loop, for example:
synchronized(lock) { while (nextVal == null) { lock.wait(); } ...
Thus, the test is executed when the thread has a lock, and everything that happens in the block below the while loop can be determined that nextVal is really not null.
source share