Problemas que o multithreading resolve em Java
Essencialmente, o multithreading Java foi inventado para resolver dois problemas principais:-
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!
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 :)
-
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.
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
. Thread
e 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 MyFirstThread
e 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) MyFirstThread
que os herdam Thread
e 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-2
e 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: 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:- 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.
- 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.
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.
GO TO FULL VERSION