Prefácio. Tio Petya
Então, digamos que queremos encher uma garrafa de água. Uma garrafa e uma torneira de água do tio Petya estão disponíveis. Tio Petya mandou instalar uma torneira nova hoje e não parava de elogiar sua beleza. Antes ele usava apenas uma torneira velha e entupida, então as filas no engarrafamento eram enormes. Depois de se atrapalhar um pouco, ouviu-se o som de água enchendo na direção do derramamento, depois de 2 minutos a garrafa ainda está em fase de enchimento, a fila de sempre se reuniu atrás de nós, e a imagem na minha cabeça é de como o tio carinhoso Petya seleciona apenas as melhores moléculas de H2O em nossa garrafa. Tio Petya, treinado pela vida, acalma os especialmente agressivos e promete acabar o mais rápido possível. Terminada a garrafa, pega a seguinte e abre a pressão habitual, o que não revela todas as capacidades da nova torneira. As pessoas não estão felizes...
Teoria
Multithreading é a capacidade de uma plataforma criar vários threads em um único processo. Criar e executar um thread é muito mais simples do que criar um processo, portanto, se for necessário implementar várias ações paralelas em um programa, threads adicionais serão usados. Na JVM, qualquer programa é executado no thread principal e o restante é iniciado a partir dele. Dentro do mesmo processo, threads são capazes de trocar dados entre si. Ao iniciar um novo thread, você pode declará-lo como um thread de usuário usando o método
setDaemon(true);
tais threads serão encerrados automaticamente se não houver mais nenhum thread em execução. Threads têm prioridade de trabalho (a escolha da prioridade não garante que o thread de maior prioridade terminará mais rápido que o thread de menor prioridade).
- MIN_PRIORITY
- NORM_PRIORITY (padrão)
- MAX_PRIORITY
Métodos básicos ao trabalhar com streams:
run()
– executa o thread
start()
– inicia um tópico
getName()
– retorna o nome do thread
setName()
– especifica o nome do fluxo
wait()
– método herdado, o thread espera que o método seja chamado notify()
de outro thread
notify()
– método herdado, retoma um thread interrompido anteriormente
notifyAll()
– método herdado, retoma threads interrompidas anteriormente
sleep()
– pausa o stream por um tempo especificado
join()
– espera a conclusão do thread
interrupt()
– interrompe a execução do thread
Mais métodos podem ser encontrados
aqui. É hora de pensar em novos threads se o seu programa tiver:
- Acesso à rede
- Acesso ao sistema de arquivos
- GUI
Classe de thread
Threads em Java são representados como uma classe
Thread
e seus descendentes. O exemplo abaixo é uma implementação simples da classe stream.
import static java.lang.System.out;
public class ExampleThread extends Thread{
public static void main(String[] args) {
out.println("Основной поток");
new ExampleThread().start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
Como resultado obtemos
Основной поток
Новый поток
Aqui criamos nossa classe e a tornamos descendente da classe
Thread
, após o que escrevemos o método main() para iniciar o thread principal e substituir o método
run()
da classe
Thread
. Agora, tendo criado uma instância de nossa classe e executado seu método herdado,
start()
lançaremos um novo thread no qual será executado tudo o que está descrito no corpo do método
run()
. Parece complicado, mas olhando o código de exemplo tudo deve ficar claro.
Interface executável
A Oracle também sugere implementar a interface para iniciar uma nova thread
Runnable
, o que nos dá mais flexibilidade de design do que a única herança disponível no exemplo anterior (se você olhar a fonte da classe
Thread
poderá ver que ela também implementa a interface
Runnable
). Vamos usar o método recomendado para criar um novo tópico.
import static java.lang.System.out;
public class ExampleRunnable implements Runnable {
public static void main(String[] args) {
out.println("Основной поток");
new Thread(new ExampleRunnable()).start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
Como resultado obtemos
Основной поток
Новый поток
Os exemplos são muito semelhantes, porque Ao escrever o código, tivemos que implementar um método abstrato
run()
descrito na interface
Runnable
. Lançar um novo tópico é um pouco diferente. Criamos uma instância da classe
Thread
passando uma referência a uma instância de nossa implementação de interface como parâmetro
Runnable
. É essa abordagem que permite criar novos threads sem herdar diretamente a classe
Thread
.
Operações longas
O exemplo a seguir mostrará claramente os benefícios do uso de vários threads. Digamos que temos uma tarefa simples que requer vários cálculos demorados, antes deste artigo teríamos resolvido ela em um método,
main()
talvez dividindo em métodos separados para facilitar a percepção, talvez até classes, mas a essência seria a mesma. Todas as operações seriam realizadas sequencialmente, uma após a outra. Vamos simular cálculos pesados e medir seu tempo de execução.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 1");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 2");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
Como resultado obtemos
complete 1
complete 2
complete 3
программа выполнялась 9885 миллисекунд
O tempo de execução deixa muito a desejar, e todo esse tempo ficamos olhando para uma tela de saída em branco, e a situação é muito parecida com a história do tio Petya, só que agora em seu papel nós, desenvolvedores, não aproveitamos todos os recursos dos dispositivos modernos. Nós vamos melhorar.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
new MyThread(1).start();
new MyThread(2).start();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
class MyThread extends Thread{
int n;
MyThread(int n){
this.n = n;
}
@Override
public void run() {
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete " + n);
}
}
Como resultado obtemos
complete 1
complete 2
complete 3
программа выполнялась 3466 миллисекунд
O tempo de execução foi reduzido significativamente (este efeito pode não ser alcançado ou pode até aumentar o tempo de execução em processadores que não suportam multithreading). Vale ressaltar que os threads podem terminar fora de ordem, e caso o desenvolvedor precise de previsibilidade de ações, ele deve implementá-la de forma independente para um caso específico.
Grupos de tópicos
Threads em Java podem ser combinados em grupos; a classe é usada para isso
ThreadGroup
. Os grupos podem incluir threads únicos e grupos inteiros. Isto pode ser conveniente se você precisar interromper fluxos associados, por exemplo, à rede quando a conexão for perdida. Você pode ler mais sobre grupos
aqui. Espero que agora o assunto tenha ficado mais claro para você e que seus usuários fiquem felizes.
GO TO FULL VERSION