O synchronized faz o bloqueio de uma thread, que está percorrendo um objeto. Para que ele tenha efeito, duas threads tem que tentar entrar no bloco sincronizado do mesmo objeto.
Nenhuma das threads criadas por você percorre o método main. Elas iniciam no run(), imprimem duas vezes e morrem. Por isso, seu synchronized não está servindo para absolutamente nada. Elas também não entram em nenhum bloco sincronizado, já que nem o método run() e nem o me() são sincronizados.
Aqui vai um exemplo do bloco sincronized. Primeiro, vamos declarar uma classe que faz a impressão de qualquer coisa 20 vezes.
public class Printer {
public void print(String word) {
for (int i = 0; i < 20; i++) {
System.out.println(word + "-" + i);
}
}
}
Agora, vamos criar 2 classes, que usam essa classe para imprimir "Vinicius" e "Fabio", que usa a impressora 20 vezes (e, portanto, imprime o nome 400x).
[code]public class NameWriter implements Runnable {
private String name;
private Printer printer;
public NameWriter(String name, Printer printer) {
this.name = name;
}
public void run() {
for (int i = 0; i < 20; i++) {
printer.print(name);
}
}
}
[/code]
Ok, agora dispare esse código com o seguinte main:
[code]public class Teste {
public static void main(String args[]) {
Printer p1 = new Printer();
//Criamos dois NameWriters usando A MESMA impressora
NameWriter nw1 = new NameWriter(“Vinicius”, p1);
NameWriter nw2 = new NameWriter(“Fabio”, p1);
//Disparamos o método run dos namewriters em 2 threads
new Thread(nw1).start();
new Thread(nw2).start();
}
}[/code]
O que parece que vai acontecer? Que os nomes serão impressos de 20 em 20, afinal existe uma única impressora e seu método imprime o nome 20 vezes de uma só vez. Assim, dois nomes não vão se intercalar, certo?
Entretanto, ao rodar o programa não é isso que acontece… alguns nomes aparecem intercalados e as vezes a impressora "se perde". Por que isso ocorre?
Porque ambas as threads entram no método print ao mesmo tempo.
Note que eu não chamei o NameWriter de thread, porque ele não é uma thread. Duas threads foram criadas percorrendo objetos do tipo Namewriter e, quando as linhas de execução chamaram o método print, elas se deslocaram para outro objeto. Isso é um conceito muito importante! Threads são as linhas de execução criadas após o start(), não os objetos.
Como evitaríamos isso? O fato é que duas threads não podem "cohabitar" o método print. O método print deveria imprimir tudo de uma thread, e depois tudo de outra. Para fazer essa exclusão mútua, usamos a palavra chave synchronized.
Quando uma thread entra num bloco synchronized, ela tenta obter o objeto ao qual estamos aplicando a sincronização. Esse objeto é chamado de monitor. Se esse objeto estiver disponível, ela segura o monitor e entra no bloco. Quando sair do bloco, ela libera o monitor. Quando uma thread tenta entrar no bloco, mas não consegue obter o monitor, ela simplesmente aguarda até que aquele objeto esteja disponível.
E que objeto é esse? Num bloco synchronized, o objeto é o this. No caso de criarmos esse bloco na impressora, o objeto seria a própria impressora p1. Ou seja, fazer na impressora:
public synchronized void print(String word) {
for (int i = 0; i < 20; i++) {
System.out.println(word + "-" + i);
}
É o mesmo que fazer:
public void print(String word) {
synchronized (this) {
for (int i = 0; i < 20; i++) {
System.out.println(word + "-" + i);
}
}
Experimente rodar o código com o synchronized. Você vai ver que o problema não ocorre mais. Os nomes sempre são impressos de 20 em 20.
Isso tem uma importante consequência. Duas threads só serão sincronizadas se o objeto this do bloco synchronized for o mesmo. Experimente manter o bloco synchronized e criar duas impressoras ao invés de uma. Depois, passe uma impressora para cada NameWriter. Você vai notar que o problema "volta a ocorrer". Na verdade, o que acontece é que threads diferentes estão percorrendo impressoras diferentes. Portanto, o this de cada impressora refere-se a uma impressora diferente e não há sincronização.
Isso porque, na verdade, o problema não voltou a ocorrer, você vai notar que os fors nunca se perdem e, apesar de os nomes se intercalarem, os nomes serão impressos 400 vezes. O que você tem é uma execução realmente em paralelo, em duas impressoras diferentes.
Uma das formas de voltar a sincronizar seria fazer as duas impressoras compartilharem um objeto, e fazer o synchronized sobre esse objeto. O objeto monitor pode ser um objeto qualquer. Deixo como exercício para você fazer essa alteração.
Qualquer dúvida é só perguntar.