Antes de explicar esse problema, vamos diferenciar algumas coisas:
a) O método wait() é um método de um objeto, não de uma thread. Ele faz com que a thread atual pare, até que outra thread chame o método notify() sobre o mesmo objeto.
b) Para toda thread criada em Runtime, teremos a thread em si (a linha de execução) e um objeto, que representa essa thread, e é filho da classe Thread. Note que a thread e o objeto que a representa são coisas diferentes.
c) Para que os métodos wait() e notify() possam ser chamados, o objeto tem que ser o monitor. Você faz isso colocando ele como parâmetro de um bloco synchronized.
Pois bem, nesse exemplo, criamos um objeto do tipo Thread, e o chamamos de t. Depois, usamos ele para disparar outra thread. A outra thread não tem qualquer relevância para a analise do problema. Ela provavelmente vai iniciar e morrer logo em seguida, já que nem runnable tem.
O que interessa mesmo é o que acontece na main thread. Ela tem um método main sincronizado. Isso significa que o monitor está na ClassDoMain.class e não no objeto t. E é no objeto t que o wait está sendo chamado. Quando o método wait() for invocado a exceção de que o objeto t não é um monitor será lançada.
A correção para esse código ficaria:
Given:
public static void main( String[] args ) throws InterruptedException {
Thread t = new Thread();
t.start();
System.out.print( "X" );
synchronized (t) {
t.wait( 10000 );
}
System.out.print( "Y" );
}
Note que t poderia pertencer a qualquer outra classe, como String. Pouco importa dele ser um objeto que descreve uma thread ou não.