CodeGym /Blogue Java /Random-PT /Multithreading em Java
John Squirrels
Nível 41
San Francisco

Multithreading em Java

Publicado no grupo Random-PT
Oi! Em primeiro lugar, parabéns: você chegou ao tópico Multithreading em Java! Esta é uma conquista séria - você percorreu um longo caminho. Mas prepare-se: esse é um dos temas mais difíceis do curso. E não é que estejamos usando classes complexas ou muitos métodos aqui: na verdade, usaremos menos de vinte. É mais que você precisará mudar um pouco a forma como pensa. Anteriormente, seus programas eram executados sequencialmente. Algumas linhas de código vieram depois de outras, alguns métodos vieram depois de outros e tudo estava basicamente claro. Primeiro, calculamos algo, depois exibimos o resultado no console e o programa terminou. Para entender multithreading, é melhor pensar em termos de paralelismo. Vamos começar com algo bem simples: ) Imagine que sua família está se mudando de uma casa para outra. Reunir todos os seus livros será uma parte importante da mudança. Você acumulou muitos livros e precisa colocá-los em caixas. Atualmente, você é o único disponível. A mãe está preparando a comida, o irmão está arrumando as roupas e a irmã foi ao mercado. Sozinho, você pode gerenciar de alguma forma. Mais cedo ou mais tarde, você mesmo concluirá a tarefa, mas levará muito tempo. No entanto, sua irmã voltará da loja em 20 minutos e ela não tem mais nada para fazer. Então ela pode se juntar a você. A tarefa não mudou: colocar livros em caixas. Mas está sendo executado duas vezes mais rápido. Por que? Porque o trabalho está acontecendo em paralelo. Dois 'threads' diferentes (você e sua irmã) estão realizando a mesma tarefa simultaneamente. E se nada mudar, então haverá uma enorme diferença de tempo em comparação com a situação em que você faz tudo sozinho. Se o irmão terminar seu trabalho em breve, ele poderá ajudá-lo e as coisas serão ainda mais rápidas.

Problemas resolvidos por multithreading

O multithreading foi realmente inventado para atingir dois objetivos importantes:
  1. Faça várias coisas ao mesmo tempo.

    No exemplo acima, diferentes threads (membros da família) realizaram várias ações em paralelo: lavaram a louça, foram ao armazém, empacotaram as coisas.

    Podemos oferecer um exemplo mais relacionado à programação. Suponha que você tenha um programa com uma interface de usuário. Ao clicar em 'Continuar' no programa, alguns cálculos devem acontecer e o usuário deve ver a seguinte tela. Se essas ações fossem executadas sequencialmente, o programa simplesmente travaria depois que o usuário clicasse no botão 'Continuar'. O usuário verá a tela com a tela do botão 'Continuar' até que o programa realize todos os cálculos internos e chegue à parte onde a interface do usuário é atualizada.

    Bem, acho que vamos esperar alguns minutos!

    Multithreading em Java: o que é, seus benefícios e armadilhas comuns - 3

    Ou poderíamos retrabalhar nosso programa ou, como dizem os programadores, 'paralelizá-lo'. Vamos realizar nossos cálculos em um thread e desenhar a interface do usuário em outro. A maioria dos computadores tem recursos suficientes para fazer isso. Se seguirmos esse caminho, o programa não congelará e o usuário se moverá suavemente entre as telas sem se preocupar com o que está acontecendo dentro. Um não interfere no outro :)

  2. Realize cálculos mais rapidamente.

    Tudo é muito mais simples aqui. Se nosso processador tiver vários núcleos, e a maioria dos processadores hoje tem, vários núcleos podem lidar com nossa lista de tarefas em paralelo. Obviamente, se precisamos realizar 1000 tarefas e cada uma leva um segundo, um núcleo pode terminar a lista em 1000 segundos, dois núcleos em 500 segundos, três em pouco mais de 333 segundos, etc.

Mas, como você já leu nesta lição, os sistemas atuais são muito inteligentes e, mesmo em um núcleo de computação, são capazes de obter paralelismo, ou melhor, pseudoparalelismo, onde as tarefas são executadas alternadamente. Vamos passar das generalidades para as específicas e conhecer a classe mais importante da biblioteca multithreading Java — java.lang.Thread. Estritamente falando, os encadeamentos Java são representados por instâncias da classe Thread . Isso significa que para criar e executar 10 threads, você precisa de 10 instâncias dessa 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 executar threads, precisamos criar uma classe, torná-la herdada do java.lang . Thread e substitua seu método run() . Esse último requisito é muito importante. É no método run() que definimos a lógica para nossa thread executar. Agora, se criarmos e executarmos uma instância de MyFirstThread , o método run() exibirá uma linha com um nome: o método getName() exibe o nome do 'sistema' do thread, que é atribuído automaticamente. Mas por que estamos falando provisoriamente? Vamos criar um e descobrir!

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 Vamos criar 10 threads ( objetos MyFirstThread , que herdam Thread ) e iniciá-los chamando o método start() em cada objeto. Depois de chamar o método start() , a lógica no método run() é executada. Nota: os nomes dos threads não estão em ordem. É estranho que eles não fossem sequencialmente:, Thread-1 , Thread-2 e assim por diante? Acontece que este é um exemplo de um momento em que o pensamento 'sequencial' não se encaixa. O problema é que fornecemos apenas comandos para criar e executar 10 threads. O agendador de threads, um mecanismo especial do sistema operacional, decide sua ordem de execução. Seu design preciso e estratégia de tomada de decisão são tópicos para uma discussão profunda na qual não vamos nos aprofundar agora. A principal coisa a lembrar é que o programador não pode controlar a ordem de execução dos threads. Para entender a gravidade da situação, tente executar o método main() no exemplo acima mais algumas vezes. Saída do console na segunda execução: Eu sou o 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 Saída do console da terceira execução: I'm 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 criados por multithreading

Em nosso exemplo com livros, você viu que o multithreading resolve tarefas muito importantes e pode tornar nossos programas mais rápidos. Muitas vezes, muitas vezes mais rápido. Mas o multithreading é considerado um tópico difícil. Na verdade, se usado de forma inadequada, cria problemas em vez de resolvê-los. Quando digo 'cria problemas', não quero dizer em algum sentido abstrato. Existem dois problemas específicos que o multithreading pode criar: deadlock e condições de corrida. Deadlock é uma situação em que vários threads estão esperando por recursos mantidos um pelo outro e nenhum deles pode continuar em execução. Falaremos mais sobre isso nas próximas lições. O exemplo a seguir será suficiente por enquanto: Multithreading em Java: o que é, seus benefícios e armadilhas comuns - 4Imagine que Thread-1 interage com algum Object-1 e que Thread-2 interage com Object-2. Além disso, o programa é escrito de modo que:
  1. Thread-1 para de interagir com Object-1 e muda para Object-2 assim que Thread-2 para de interagir com Object-2 e muda para Object-1.
  2. Thread-2 para de interagir com Object-2 e muda para Object-1 assim que Thread-1 para de interagir com Object-1 e muda para Object-2.
Mesmo sem um conhecimento profundo de multithreading, você pode ver facilmente que nada acontecerá. Os threads nunca trocarão de lugar e esperarão um pelo outro para sempre. O erro parece óbvio, mas na realidade não é. Você pode facilmente fazer isso em um programa. Consideraremos exemplos de código que causam impasse nas lições subsequentes. A propósito, o Quora tem um ótimo exemplo da vida real que explica o que é um impasse.é. “Em alguns estados da Índia, eles não vendem terras agrícolas a menos que você seja um agricultor registrado. No entanto, eles não o registrarão como agricultor se você não possuir terras agrícolas'. Ótimo! O que podemos dizer?! :) Agora vamos falar sobre as condições da corrida. Uma condição de corrida é um erro de design em um sistema ou aplicativo multithread, em que a operação do sistema ou aplicativo depende da ordem na qual as partes do código são executadas. Lembre-se, nosso exemplo onde começamos os tópicos:

public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Thread executed: " + 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 seja responsável por comandar um robô que cozinha comida! Thread-0 tira os ovos da geladeira. O fio-1 liga o fogão. Fio-2 pega uma panela e coloca no fogão. Fio-3 acende o fogão. Thread-4 derrama óleo na panela. Fio-5 quebra os ovos e os coloca na panela. Thread-6 joga as cascas de ovo na lata de lixo. O fio-7 remove os ovos cozidos do queimador. Fio-8 coloca os ovos cozidos em um prato. Fio-9 lava a louça. Veja os resultados do nosso programa: Tópico executado: Tópico-0 Tópico executado: Tópico-2 Tópico executado Tópico-1 Tópico executado: Tópico-4 Tópico executado: Tópico-9 Tópico executado: Tópico-5 Tópico executado: Tópico-8 Tópico executado: Thread-7 Thread executado: Thread-3 Isso é uma rotina de comédia? :) E tudo porque o funcionamento do nosso programa depende da ordem de execução dos threads. Dada a menor violação da sequência exigida, nossa cozinha se transforma em um inferno e um robô insano destrói tudo ao seu redor. Este também é um problema comum na programação multithread. Você vai ouvir sobre isso mais de uma vez. Ao concluir esta lição, gostaria de recomendar um livro sobre multithreading. Multithreading em Java: o que é, seus benefícios e armadilhas comuns - 6'Java Concurrency in Practice' foi escrito em 2006, mas não perdeu sua relevância. Ele é dedicado à programação Java multithreaded — desde o básico até os erros e antipadrões mais comuns. Se algum dia você decidir se tornar um guru multithreading, este livro é uma leitura obrigatória. Nos vemos nas próximas aulas! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION