@tnine when the anonymous Runnable's Thread enters the wait() on line #275 it releases the monitor on changes and reacquires it once it wakes up. While waiting other Threads (the ones executing queueNotifications()) can still make modifications on changes (enqueuing new changes). Plus I cannot call wait() without acquiring the monitor. So the while loop still needs to be inside the synch block. The monitor is not held all the time in the while loop if that's what you were suggesting.

Thanks however to your making me reconsider things I found a different problem: I forgot to synchronize on lines #293 and #294 on the changes object monitor. And I spelled out "wake" instead of "woke" in the LOG error message :D.