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:
- 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. 