"Olá, amigo! Temos uma panacéia — uma cura para todas as doenças. Como já vimos, a troca descontrolada de threads é um problema."

"Por que os próprios threads não podem decidir quando mudar para o próximo thread? Faça tudo o que eles precisam fazer e então sinalize: «Terminei!»?"

"Permitir que os próprios threads controlem a troca seria um problema ainda maior. Suponha que você tenha um código mal escrito e o thread nunca renuncie à CPU. Antigamente, era assim que funcionava. E era um pesadelo e tanto."

"Tudo bem. Então, qual é a solução?"

" Bloqueando outros tópicos.  É assim que funciona."

Ficou claro que as threads interferem umas nas outras quando tentam usar objetos e/ou recursos compartilhados . Assim como vimos no exemplo com a saída do console: há um console e todos os threads são enviados para ele. É bagunçado.

Assim, um objeto especial foi inventado: o mutex . É como uma placa na porta do banheiro que diz «disponível / ocupado» . Ele tem dois estados: o objeto está disponível ou ocupado . Esses estados também são chamados de «bloqueado» e «desbloqueado».

Quando um thread precisa de um objeto compartilhado com outros threads, ele verifica o mutex associado ao objeto. Se o mutex for desbloqueado, o thread o bloqueia (marca-o como «ocupado») e começa a usar o recurso compartilhado. Após o thread ter feito seu trabalho, o mutex é desbloqueado (marcado como «disponível»).

Se o thread quiser usar o objeto e o mutex estiver bloqueado, o thread ficará suspenso enquanto espera. Quando o mutex for finalmente desbloqueado pelo thread de ocupação, nosso thread irá bloqueá-lo imediatamente e começar a executar. A analogia com uma placa de porta de banheiro é perfeita.

"Então, como trabalho com um mutex? Preciso criar objetos especiais?"

"É muito mais simples do que isso. Os criadores de Java criaram esse mutex na classe Object. Portanto, você nem precisa criá-lo. Faz parte de todos os objetos. Veja como tudo funciona:"

Código Descrição
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
O método swap troca os valores das variáveis ​​name1 e name2.

O que pode acontecer se for chamado de dois threads ao mesmo tempo?

Execução real do código Código da primeira thread Código da segunda thread
String s1 = name1; //Ally
name1 = name2; //Lena
name2 = s1; //Ally

String s2 = name1; //Lena
name1 = name2; //Ally
name2 = s2; //Lena
String s1 = name1;
name1 = name2;
//other thread is running
name2 = s1;
//the thread waits until the mutex is unlocked

String s2 = name1;
name1 = name2;
//other thread is running
//other thread is running
name2 = s2;
A linha de fundo
Os valores das variáveis ​​foram trocados duas vezes, retornando aos seus lugares originais.

Preste atenção à palavra-chave  sincronizado .

"Sim, o que isso significa?"

"Quando um thread entra em um bloco de código marcado como sincronizado, a máquina Java bloqueia imediatamente o mutex do objeto indicado entre parênteses após a palavra sincronizado. Nenhum outro thread pode entrar neste bloco até que nosso thread saia dele. Assim que nosso thread sair o bloco marcado como sincronizado, o mutex é imediata e automaticamente desbloqueado e ficará disponível para ser adquirido por outra thread."

Se o mutex estiver ocupado, nosso thread ficará parado e esperará que ele seja liberado.

"Tão simples e tão elegante. É uma bela solução."

"Sim. Mas o que você acha que vai acontecer neste caso?"

Código Descrição
class MyClass
{
private String name1 = "Ally";
private String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
Os métodos swap e swap2 compartilham o mesmo mutex (o objeto this ).

O que acontece se uma thread chamar o método swap e outra thread chamar o método swap2?

"Como o mutex é o mesmo, o segundo thread terá que esperar até que o primeiro thread saia do bloco sincronizado. Portanto, não haverá problemas com acesso simultâneo."

"Muito bem, amigo! Essa é a resposta correta!"

Agora eu gostaria de apontar que sincronizado pode ser usado para marcar não apenas blocos de código, mas também métodos. Aqui está o que isso significa:

Código O que realmente acontece
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Ally";
private static String name2 = "Lena";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}