Problemas resolvidos por multithreading
O multithreading foi realmente inventado para atingir dois objetivos importantes:-
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!
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 :)
-
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.
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: Imagine 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:- 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.
- 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.
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. '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! :)
GO TO FULL VERSION