JavaRush /Blogue Java /Random-PT /Multithreading em Java: essência, vantagens e armadilhas ...

Multithreading em Java: essência, vantagens e armadilhas comuns

Publicado no grupo Random-PT
Olá! Em primeiro lugar, parabéns: você chegou ao tópico Multithreading em Java! Esta é uma conquista séria, há um longo caminho a percorrer. Mas prepare-se: esse é um dos temas mais difíceis do curso. E a questão não é que classes complexas ou muitos métodos sejam usados ​​​​aqui: pelo contrário, não existem nem duas dúzias. É mais que você precisa mudar um pouco o seu pensamento. Anteriormente, seus programas eram executados sequencialmente. Algumas linhas de código seguiram outras, alguns métodos seguiram outros e, no geral, tudo ficou claro. Primeiro, calcule algo, depois exiba o resultado no console e encerre o programa. Para entender o multithreading, é melhor pensar em termos de simultaneidade. Vamos começar com algo muito simples :) Multithreading em Java: essência, vantagens e armadilhas comuns - 1Imagine que sua família está se mudando de uma casa para outra. Uma parte importante da mudança é embalar seus livros. Você acumulou muitos livros e precisa colocá-los em caixas. Agora só você está livre. A mãe está preparando comida, o irmão está recolhendo roupas e a irmã foi ao armazém. Sozinho você consegue, pelo menos, e, mais cedo ou mais tarde, até mesmo completará a tarefa sozinho, mas isso levará muito tempo. Porém, em 20 minutos sua irmã retornará da loja e ela não terá mais nada para fazer. Então ela pode se juntar a você. A tarefa permaneceu a mesma: colocar os livros em caixas. Ele funciona duas vezes mais rápido. Por que? Porque o trabalho é feito em paralelo. Dois “fios” diferentes (você e sua irmã) estão executando simultaneamente a mesma tarefa e, se nada mudar, a diferença de tempo será muito grande se comparada a uma situação em que você faria tudo sozinho. Se seu irmão concluir sua tarefa logo, ele poderá ajudá-lo e as coisas serão ainda mais rápidas.

Problemas que o multithreading resolve em Java

Essencialmente, o multithreading Java foi inventado para resolver dois problemas principais:
  1. Execute várias ações ao mesmo tempo.

    No exemplo acima, diferentes segmentos (ou seja, familiares) realizaram diversas ações em paralelo: lavaram a louça, foram à loja, dobraram as coisas.

    Um exemplo mais “programador” pode ser dado. Imagine que você tem um programa com interface de usuário. Ao clicar no botão Continuar, alguns cálculos deverão ocorrer dentro do programa e o usuário deverá ver a seguinte tela de interface. Se essas ações forem realizadas sequencialmente, após clicar no botão “Continuar”, o programa simplesmente irá congelar. O usuário verá a mesma tela com um botão “Continuar” até que todos os cálculos internos sejam concluídos e o programa chegue à parte onde a interface começará a ser desenhada.

    Bem, vamos esperar alguns minutos!

    Multithreading em Java: essência, vantagens e armadilhas comuns - 3

    Também podemos refazer nosso programa ou, como dizem os programadores, “paralelizá-lo”. Deixe os cálculos necessários serem realizados em um thread e a renderização da interface em outro. A maioria dos computadores possui recursos suficientes para isso. Nesse caso, o programa não será “estúpido” e o usuário se movimentará com tranquilidade entre as telas da interface, sem se preocupar com o que está acontecendo lá dentro. Não interfere :)

  2. Acelere os cálculos.

    Tudo é muito mais simples aqui. Se nosso processador tiver vários núcleos, e a maioria dos processadores agora for multi-core, nossa lista de tarefas poderá ser resolvida em paralelo por vários núcleos. Obviamente, se precisarmos resolver 1.000 problemas e cada um deles for resolvido em um segundo, um núcleo irá lidar com a lista em 1.000 segundos, dois núcleos em 500 segundos, três em pouco mais de 333 segundos e assim por diante.

Mas, como você já leu na palestra, os sistemas modernos são muito inteligentes e, mesmo em um núcleo de computação, são capazes de implementar paralelismo, ou pseudo-paralelismo, quando as tarefas são executadas alternadamente. Vamos passar do geral para o específico e nos familiarizar com a classe principal da biblioteca Java relacionada ao multithreading - java.lang.Thread. A rigor, threads em Java são representados por instâncias da classe Thread. Ou seja, para criar e executar 10 threads, você precisará de 10 objetos desta classe. Vamos escrever o exemplo mais simples:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
Para criar e lançar threads, precisamos criar uma classe e herdá-la do arquivo java.lang. Threade substitua o método nele run(). O último é muito importante. É no método que run()prescrevemos a lógica que nossa thread deve executar. Agora, se criarmos uma instância MyFirstThreade executá-la, o método run()imprimirá uma linha com seu nome no console: o método getName()imprime o nome de “sistema” do thread, que é atribuído automaticamente. Embora, na verdade, por que “se”? Vamos criar e testar!
public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Saída do console: Sou Thread! Meu nome é Thread-2, sou Thread! Meu nome é Thread-1. Sou Thread! Meu nome é Thread-0, sou Thread! Meu nome é Thread-3, sou Thread! Meu nome é Thread-6, sou Thread! Meu nome é Thread-7, sou Thread! Meu nome é Thread-4, sou Thread! Meu nome é Thread-5, sou Thread! Meu nome é Thread-9, sou Thread! Meu nome é Thread-8. Criamos 10 threads (objetos) MyFirstThreadque os herdam Threade os iniciamos chamando o método do objeto start(). Após chamar um método , start()seu método começa a funcionar run()e a lógica que foi escrita nele é executada. Atenção: os nomes dos tópicos não estão em ordem. É muito estranho, por que eles não foram executados sucessivamente: Thread-0, Thread-1, Thread-2e assim por diante? Este é exatamente um exemplo de quando o pensamento “sequencial” padrão não funciona. O fato é que neste caso emitimos apenas comandos para criar e lançar 10 threads. A ordem em que eles devem ser iniciados é decidida pelo agendador de threads: um mecanismo especial dentro do sistema operacional. Como exatamente está estruturado e com base em que princípios toma decisões é um tema muito complexo, e não vamos nos aprofundar nele agora. A principal coisa a lembrar é que o programador não pode controlar a sequência de execução do thread. Para perceber a gravidade da situação, tente executar o método main()do exemplo acima mais algumas vezes. Segunda saída do console: Sou Thread! Meu nome é Thread-0, sou Thread! Meu nome é Thread-4, sou Thread! Meu nome é Thread-3, sou Thread! Meu nome é Thread-2, sou Thread! Meu nome é Thread-1. Sou Thread! Meu nome é Thread-5, sou Thread! Meu nome é Thread-6, sou Thread! Meu nome é Thread-8, sou Thread! Meu nome é Thread-9, sou Thread! Meu nome é Thread-7 Terceira saída do console: Sou Thread! Meu nome é Thread-0, sou Thread! Meu nome é Thread-3, sou Thread! Meu nome é Thread-1. Sou Thread! Meu nome é Thread-2, sou Thread! Meu nome é Thread-6, sou Thread! Meu nome é Thread-4, sou Thread! Meu nome é Thread-9, sou Thread! Meu nome é Thread-5, sou Thread! Meu nome é Thread-7, sou Thread! Meu nome é Thread-8

Problemas que o multithreading cria

No exemplo dos livros, você viu que o multithreading resolve problemas bastante importantes e seu uso agiliza o trabalho dos nossos programas. Em muitos casos – muitas vezes. Mas não é à toa que o multithreading é considerado um tema complexo. Afinal, se usado incorretamente, cria problemas em vez de resolvê-los. Quando digo “criar problemas”, não quero dizer algo abstrato. Existem dois problemas específicos que o multithreading pode causar: conflito e condição de corrida. Deadlock é uma situação em que vários threads aguardam recursos ocupados uns pelos outros e nenhum deles pode continuar em execução. Falaremos mais sobre isso em palestras futuras, mas por enquanto este exemplo será suficiente: Multithreading em Java: essência, vantagens e armadilhas comuns - 4 Imagine que a thread-1 está trabalhando com algum Objeto-1, e a thread-2 está trabalhando com o Objeto-2. O programa está escrito assim:
  1. Thread-1 irá parar de funcionar com o Objeto-1 e mudar para o Objeto-2 assim que o Thread-2 parar de funcionar com o Objeto 2 e mudar para o Objeto-1.
  2. Thread-2 irá parar de funcionar com o Objeto-2 e mudar para o Objeto-1 assim que o Thread-1 parar de funcionar com o Objeto 1 e mudar para o Objeto-2.
Mesmo sem um conhecimento profundo de multithreading, você pode facilmente entender que nada resultará disso. Os fios nunca mudarão de lugar e esperarão um pelo outro para sempre. O erro parece óbvio, mas na realidade não é. Você pode facilmente permitir isso no programa. Veremos exemplos de código que causam impasse nas aulas a seguir. A propósito, o Quora tem um excelente exemplo da vida real explicando o que é um impasse . “Em alguns estados da Índia, não lhe venderão terras agrícolas a menos que esteja registado como agricultor. No entanto, você não será registrado como agricultor se não possuir terras agrícolas.” Ótimo, o que posso dizer! :) Agora sobre a condição da corrida – o estado da corrida. Multithreading em Java: essência, vantagens e armadilhas comuns - 5Uma condição de corrida é uma falha de projeto em um sistema ou aplicativo multithread em que a operação do sistema ou aplicativo depende da ordem em que partes do código são executadas. Lembre-se do exemplo com threads em execução:
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Выполнен поток " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Agora imagine que o programa é responsável pelo funcionamento de um robô que prepara alimentos! Thread-0 tira os ovos da geladeira. A corrente 1 liga o fogão. Stream-2 pega uma frigideira e coloca no fogão. O fluxo 3 acende o fogão. A corrente 4 despeja óleo na panela. A corrente 5 quebra os ovos e os coloca na frigideira. A corrente 6 joga as conchas na lixeira. Stream-7 remove os ovos mexidos prontos do fogo. Potok-8 coloca ovos mexidos em um prato. Stream 9 lava pratos. Veja os resultados do nosso programa: Thread-0 executado Thread-2 thread executado Thread-1 thread executado Thread-4 thread executado Thread-9 thread executado Thread-5 thread executado Thread-8 thread executado Thread-7 thread executado Thread-7 thread executado -3 Thread-6 thread executado O script é divertido? :) E tudo porque o funcionamento do nosso programa depende da ordem em que os threads são executados. À menor violação da sequência, nossa cozinha vira um inferno, e um robô enlouquecido destrói tudo ao seu redor. Esse também é um problema comum na programação multithread, sobre o qual você ouvirá falar mais de uma vez. Ao final da palestra, gostaria de recomendar um livro sobre multithreading.
Multithreading em Java: essência, vantagens e armadilhas comuns - 6
“Java Concurrency in Practice” foi escrito em 2006, mas não perdeu sua relevância. Abrange a programação multithread em Java, começando pelo básico e terminando com uma lista dos erros e antipadrões mais comuns. Se você decidir se tornar um guru da programação multithread, este livro é uma leitura obrigatória. Nos vemos nas próximas palestras! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION