Vamos começar por um conceito importante.
Os mecanismos de sincronização do Java se baseiam na ideia do lock (trava). Todo objeto em tempo de execução tem um lock associado a ele. O lock é um valor especial que só pode ser possuído por no máximo uma thread.
O que isso...
synchronized(mensagens) {
// corpo do bloco
}
...significa é:
- espere
mensagens
ser destravado; - trave
mensagens
; - execute o corpo;
- destrave
mensagens
.
Agora vamos para o cerne da tua dúvida.
No primeiro caso — o que recorre em erro —, o código é:
public synchronized void run() {
for(int i = comeco; i < fim; i++) {
mensagens.add("Mensagem " + i);
}
}
O que isso significa é que tu estás sincronizando o acesso ao método run
daquela instância de ProduzMensagens
.
Porque isso não funciona? Vamos para o código feito no método main
:
Thread t1 = new Thread(new ProduzMensagens(0, 10000, mensagens));
Thread t2 = new Thread(new ProduzMensagens(10000, 20000, mensagens));
Thread t3 = new Thread(new ProduzMensagens(20000, 30000, mensagens));
Perceba que criamos três instâncias da classe ProduzMensagens
, passando cada uma como argumento numa thread diferente.
Ou seja: no código do primeiro exemplo, a palavra reservada synchronized
é inútil; o resultado é o mesmo se não a usarmos. Porque há três instâncias diferentes de ProduzMensagens
, cada uma com seu método run
.
O primeiro exemplo apenas faria sentido se todas as threads recebessem como argumento uma mesma instância de ProduzMensagens
.
Importante: qual é a intenção com o synchronized
nesse exercício? Sincronizar o acesso à coleção mensagens
.
Por isso o segundo exemplo funciona:
public void run() {
for(int i = comeco; i < fim; i++) {
synchronized(mensagens) {
mensagens.add("Mensagem " + i);
}
}
}
Passamos mensagens
como argumento para synchronized
, de modo que o lock desse objeto (mensagens
) seja possuído por apenas uma thread de cada vez.
Assim, a adição de elementos em mensagens
(mensagens.add
) é realizada uma thread por vez. Se a thread t1
possui o lock do objeto mensagens
, então só ela pode ter acesso. Quando a mesma liberar o lock, somente aí outra thread (t2
) pode obtê-lo e ter acesso exclusivo ao objeto.
Caso contrário, mensagens
seria acessada de forma intermitente pelas threads, levando à ocorrência das ArrayIndexOutOfBoundsException
e IllegalStateException
.
Espero que tenha sanado tuas dúvidas. ;)