Aquele objeto que vai dentro do bloco sincronizado, é quem controla quem pode ou não entrar no bloco. Duas threads irão se bloquear se aquele objeto for o mesmo. O notify() irá apenas notificar quem estiver esperando num wait() dado num mesmo objeto.
Primeiro, para responder à sua pergunta, é necessário entender porque existe sincronização. Sincronização existe para impedir que duas threads acessem simultaneamente a mesma área de memória. Essa área de memória é chamade de região crítica. O problema é que duas threads alterando a região crítica ao mesmo tempo podem, de vez enquando, fazer modificações indesejadas nos dados.
Pois bem. Dois objetos diferentes, dados com new diferentes, ocupam áreas de memória diferentes. Portanto, não há problema que uma thread percorra um objeto e outra outro objeto, ao mesmo tempo. Note que estou falando de objetos, não de classes.
Por isso, ocorre sincronização no this. Ela garante que cada thread acesse exclusivamente aquele objeto. E que threads possam acessar simultaneamente dois objetos distintos daquela classe. Acessar coisas de maneira simultânea é bom, é justamente o que quereremos quando trabalhamos com threads. Uma boa aplicação multi-threading tem por obrigação maximizar o paralelismo.
Entretanto, temos um tipo de variável chamado static. Esse tipo é compartilhado por todos os objetos de uma classe. Isso força com que a sincronização não possa ser feita em this. Se for feita, duas threads simultâneas irão acessar dois objetos distintos ao mesmo tempo e, quando chegarem no ponto onde o static é usado, irão altera-lo ao mesmo tempo, pois ele é compartilhado. Dessa forma, temos ali uma região crítica compartilhada, que ainda está permitindo acesso mútuo. Nesse caso, você é obrigado a usar o synchronized no mesmo objeto para todas as threads. Como garantir isso? A forma mais simples é usar no synchronized um outro objeto estático. Como esse objeto será compartilhado tanto quanto sua variável, todas as threads irão parar. Note que essa solução tem menos paralelismo que a anterior. Na verdade, ela praticamente elimina a vantagem de se trabalhar com threads, já que, para o objeto que faz isso, todas as threads serão enfileiradas, independente da instância utilizada. Esse é um dos motivos pelo qual synchronized é ruim para threads.
Bom, entendida essa diferença, vamos falar agora sobre usar o this e o class, versus usar um objeto próprio. De maneira geral, é melhor usar um objeto próprio para fazer a sincronização. Esse objeto deve ser declarado só para esse fim, e ser um atributo privado da classe:
public class Classe {
private int lock = new int[0]; //Um objeto qualquer super simples
public void metodo() {
synchronized(lock) {
//faz qualquer coisa
}
}
}
public class Classe {
private static int lock = new int[0]; //Um objeto qualquer super simples
public void metodo() {
synchronized(lock) {
//faz qualquer coisa alterando variáveis estáticas
}
}
}
O mesmo vale para statics.
Essa abordagem tem a vantagem de não gerar problemas, caso alguém resolva usar um objeto de sua classe dentro de um bloco synchronized. Entretanto, mas essa forma é um pouco verborrágica.
Se usada direito, a forma padrão do java também atende as necessidades. Isto é, usar a sincronização em this e em Classe.class. Entretanto, se você adotar esse estilo, passa a ser uma péssima prática usar objetos de outras classes para fazer a sincronização. Por exemplo:
public class Classe2 {
private Classe classe = new Classe();
public void doSomething() {
synchronized (classe) { //Ops, note que a classe Classe tem sincronização externa, sobre o mesmo lock!
}
}
}
Note que como Classe sincroniza pelo this, e Classe2 está sincronizando por um objeto do tipo Classe, os dois estão, de fato, usando a mesma chave de sincronização! E isso pode gerar coisas nefastas, como deadlocks imprevistos (e note também que a solução anterior, que apontei como melhor não sofre desse problema).
Por isso, sincronizar em outros objetos, como System.out, ou classes diferentes pode ser perigoso. O livro talvez faça isso apenas a título de exemplo, para dizer que é possível. Mas é uma péssima prática.