General comment: the javadoc of Object#wait
states that
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop.
So a waiting thread can wake up without being notified and your design should take that into account by waiting in a loop and checking for an exit condition (cf example in the javadoc).
In your case, however, the issue is slightly different. According to the Thread#join
javadoc:
As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.
So when your Calculator finishes, it calls this.notifyAll()
and wakes up all the waiting threads.
How to fix it?
You should use a separate lock object, similar to: private final Object lock = new Object();
in your Calculator and provide a getter for the Readers.