JavaRush /Blogue Java /Random-PT /Sincronização de threads. Operador sincronizado em Java

Sincronização de threads. Operador sincronizado em Java

Publicado no grupo Random-PT
Olá! Hoje continuaremos a considerar os recursos da programação multithread e falar sobre sincronização de threads.
Sincronização de threads.  Operador sincronizado - 1
O que é “sincronização”? Fora do domínio da programação, refere-se a algum tipo de configuração que permite que dois dispositivos ou programas funcionem juntos. Por exemplo, um smartphone e um computador podem ser sincronizados com uma conta Google, e uma conta pessoal em um site pode ser sincronizada com contas em redes sociais para fazer login usando-as. A sincronização de threads tem um significado semelhante: é configurar como os threads interagem entre si. Nas palestras anteriores, nossos fios viveram e funcionaram separados uns dos outros. Um estava contando alguma coisa, o segundo estava dormindo, o terceiro exibia algo no console, mas eles não interagiam entre si. Em programas reais tais situações são raras. Vários threads podem trabalhar ativamente, por exemplo, com o mesmo conjunto de dados e alterar algo nele. Isso cria problemas. Imagine que vários threads estão gravando texto no mesmo local, como um arquivo de texto ou console. Este arquivo ou console, neste caso, torna-se um recurso compartilhado. Threads não sabem da existência um do outro, então eles simplesmente anotam tudo o que podem gerenciar no tempo que o agendador de threads aloca para eles. Em uma palestra recente do curso, tivemos um exemplo do que isso levaria, vamos relembrar: Sincronização de threads.  Operador sincronizado - 2O motivo está no fato das threads trabalharem com um recurso compartilhado, o console, sem coordenar suas ações entre si. Se o agendador de threads alocou tempo para Thread-1, ele grava tudo imediatamente no console. O que outros threads já conseguiram escrever ou não conseguiram escrever não é importante. O resultado, como você pode ver, é desastroso. Portanto, na programação multithread, foi introduzido um conceito especial de mutex (do inglês “mutex”, “exclusão mútua” - “exclusão mútua”) . O objetivo de um mutex é fornecer um mecanismo para que apenas um thread tenha acesso a um objeto em um determinado momento. Se Thread-1 adquiriu o mutex do objeto A, outros threads não terão acesso a ele para alterar nada nele. Até que o mutex do objeto A seja liberado, os threads restantes serão forçados a esperar. Exemplo da vida real: imagine que você e mais 10 estranhos estão participando de um treinamento. Você precisa se revezar para expressar ideias e discutir algo. Mas, como vocês estão se vendo pela primeira vez, para não se interromperem constantemente e não entrarem em confusão, vocês usam a regra da “bola falante”: apenas uma pessoa pode falar - aquela que está com a bola dentro as mãos dele. Desta forma a discussão revela-se adequada e frutífera. Então, um mutex, em essência, é uma bola. Se o mutex de um objeto estiver nas mãos de um thread, outros threads não poderão acessar o objeto. Você não precisa fazer nada para criar um mutex: ele já está embutido na classe Object, o que significa que todo objeto em Java o possui.

Como funciona o operador sincronizado em Java

Vamos nos familiarizar com uma nova palavra-chave sincronizada . Ele marca uma determinada parte do nosso código. Se um bloco de código estiver marcado com a palavra-chave sincronizada, significa que o bloco só pode ser executado por um thread por vez. A sincronização pode ser implementada de diferentes maneiras. Por exemplo, crie um método sincronizado inteiro:
public synchronized void doSomething() {

   //...method logic
}
Ou escreva um bloco de código onde a sincronização é realizada em algum objeto:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
O significado é simples. Se um thread entra em um bloco de código marcado com a palavra sincronizado, ele adquire instantaneamente o mutex do objeto, e todos os outros threads que tentam entrar no mesmo bloco ou método são forçados a esperar até que o thread anterior conclua seu trabalho e libere o mesmo bloco ou método. monitor. Sincronização de threads.  Operador sincronizado - 3Por falar nisso! Nas palestras do curso você já viu exemplos de sincronizados, mas pareciam diferentes:
public void swap()
{
   synchronized (this)
   {
       //...method logic
   }
}
O tópico é novo para você e é claro que no início haverá confusão com a sintaxe. Portanto, lembre-se imediatamente para não se confundir posteriormente nos métodos de escrita. Esses dois métodos de escrita significam a mesma coisa:
public void swap() {

   synchronized (this)
   {
       //...method logic
   }
}


public synchronized void swap() {

   }
}
No primeiro caso, você cria um bloco de código sincronizado imediatamente após inserir o método. É sincronizado por object this, ou seja, pelo objeto atual. E no segundo exemplo você coloca a palavra sincronizado em todo o método. Não há mais necessidade de indicar explicitamente qualquer objeto no qual a sincronização é realizada. Uma vez que um método inteiro seja marcado com uma palavra, este método será automaticamente sincronizado para todos os objetos da classe. Não vamos nos aprofundar na discussão sobre qual método é melhor. Por enquanto, escolha o que você mais gosta :) O principal é lembrar: você pode declarar um método sincronizado somente quando toda a lógica dentro dele for executada por um thread ao mesmo tempo. Por exemplo, neste caso doSomething()seria um erro sincronizar o método:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...some logic available to all threads

       synchronized (obj) {

           //logic that is only available to one thread at a time
       }
   }
}
Como você pode ver, uma parte do método contém lógica para a qual a sincronização não é necessária. O código nele contido pode ser executado por vários threads simultaneamente e todos os locais críticos são alocados em um bloco sincronizado separado. E um momento. Vejamos ao microscópio nosso exemplo da palestra com troca de nomes:
public void swap()
{
   synchronized (this)
   {
       //...method logic
   }
}
Observação: a sincronização é realizada usando this. Ou seja, para um objeto específico MyClass. Imagine que temos 2 threads ( Thread-1e Thread-2) e apenas um objeto MyClass myClass. Nesse caso, se Thread-1o método for chamado myClass.swap(), o mutex do objeto estará ocupado e, Thread-2quando você tentar chamá-lo, myClass.swap()ele travará aguardando que o mutex fique livre. Se tivermos 2 threads e 2 objetos MyClass- myClass1e myClass2- em objetos diferentes, nossos threads podem facilmente executar métodos sincronizados simultaneamente. O primeiro tópico faz:
myClass1.swap();
O segundo faz:
myClass2.swap();
Neste caso, a palavra-chave sincronizada dentro do método swap()não afetará o funcionamento do programa, pois a sincronização é realizada em um objeto específico. E neste último caso temos 2 objetos, portanto os threads não criam problemas um para o outro. Afinal, dois objetos possuem 2 mutexes diferentes e sua captura não depende um do outro.

Recursos de sincronização em métodos estáticos

Mas e se você precisar sincronizar um método estático?
class MyClass {
   private static String name1 = "Olya";
   private static String name2 = "Lena";

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

}
Não está claro o que servirá como mutex neste caso. Afinal, já decidimos que todo objeto possui um mutex. Mas o problema é que para chamar um método estático MyClass.swap()não precisamos de objetos: o método é estático! Então, o que vem a seguir? :/ Na verdade, não há problema com isso. Os criadores do Java cuidaram de tudo :) Se o método que contém a lógica crítica “multithreaded” for estático, a sincronização será realizada por classe. Para maior clareza, o código acima pode ser reescrito como:
class MyClass {
   private static String name1 = "Olya";
   private static String name2 = "Lena";

   public static void swap() {

       synchronized (MyClass.class) {
           String s = name1;
           name1 = name2;
           name2 = s;
       }
   }

}
Em princípio, você poderia ter pensado nisso sozinho: como não há objetos, o mecanismo de sincronização deve de alguma forma ser “conectado” às próprias classes. É assim mesmo: você também pode sincronizar entre classes.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION