Having this wait declaration:
public final native void wait(long timeout) throws InterruptedException;
It could exit by InterruptedExceptio
There is one more reason that notify can return: spurious wakeup. This is an unlikely but possible thing, because preventing spurious wakeups is very expensive on some hardware/OS combinations.
Because of this you always have to call wait() in a loop and re-check the condition that you are waiting for. During this work it's easy to check for timeout at the same time.
For details I recommend the book "Java Concurrency In Practice". And using higher level constructs that will get this all correct for you.