Olá, meu nome é Iasmin Araújo, faço parte da equipe da Escola de Programação e dou as boas-vindas para mais um curso de Java na Alura.
Audiodescrição: Iasmin se identifica como uma mulher branca. Possui cabelos castanho-escuros longos e olhos verdes. No corpo, usa uma camiseta preta. Está sentada em uma cadeira gamer preta. Ao fundo, há um quarto com iluminação azul.
Neste curso, vamos falar de threads, mas não são as threads do Twitter (atual X), nem do Zuckerberg. Vamos falar das threads que criamos com Java.
Este conteúdo é para quem já tem uma base em Java — portanto, já conhece classes, objetos, interfaces e collections (coleções) — e já viu o conteúdo de Spring Boot, além de ter prática com isso.
Vamos explorar tudo isso em dois projetos diferentes. Primeiro, vamos trabalhar com um projeto de linha de comando, que é uma simulação de um banco. Nesse projeto, vamos ver primeiro como criar as threads manualmente e manipulá-las de certa forma.
Depois que tivermos essa prática com as threads, vamos para um projeto mais avançado, que utiliza Spring Boot e é mais parecido com o que vemos no mundo real. Esse projeto é a AdoPet Store, uma nova parte da AdoPet que vende produtos para os pets adotados.
Para aproveitar todos os recursos da plataforma, você pode assistir os vídeos, fazer todas as atividades e contar com o apoio do fórum e da comunidade do Discord.
Vamos começar nosso desenvolvimento!
Quando estamos trabalhando com programação, sempre queremos simular as situações reais da melhor forma possível. Para armazenar as informações e lidar com elas, precisamos representá-las da melhor forma no código.
Uma situação muito comum quando estamos trabalhando com programação é querer simular coisas que acontecem paralelamente. Vamos pensar em um exemplo. Suponhamos que somos clientes de um banco X, e nele, temos uma conta e um cartão de débito.
Esse cartão fornece um cartão de débito adicional que podemos dar a outra pessoa. Contudo, ocorre uma falha de comunicação e as duas pessoas que possuem os cartões resolvem ir ao banco e sacar todo o dinheiro na conta ao mesmo tempo. Como podemos representar isso no código com Java da melhor forma possível?
Para ver como podemos fazer isso, vamos utilizar um projeto do qual temos a parte inicial do código já disponível. Recomendamos que você o clone e vá "codando" conosco.
Vamos abrir no IntelliJ o projeto já importado. Nele, temos alguns arquivos com classes bem básicas:
Cliente
, com uma string nome
e o construtor com um getNome()
;Conta
, que tem um Cliente
e o saldo
que ele tem na conta eOperacaoSaque
que possui uma Conta
, o valor
a sacar, um construtor e o método executa
para realizar um saque, no qual há alguns logs com a lógica para iniciar e finalizar o saque.Na lógica do log dentro do método executa
, se o saldo atual for maior do que o valor que queremos sacar, podemos debitar o valor da conta. Caso contrário, não entramos no if
e só finalizamos o saque.
Temos essas regras básicas. Esse é um projeto que está em evolução, no qual vamos adicionar algumas regras.
Como mencionado, queremos simular os dois saques ao mesmo tempo. Para isso, vamos criar uma classe para lidar com essas contas.
main
Podemos criar uma classe main
e uma conta para ver como vamos sacar. No explorador de arquivos, na lateral esquerda da IDE, vamos clicar no pacote "br.com.alura" e usar "Alt+Insert" para criar uma nova classe. Ele exibirá uma janela flutuante chamada "New", na qual vamos selecionar a primeira opção, "Java Class".
Na janela "New Java Class", vamos adicionar o nome da classe, que vamos chamar de AppBanco
, e pressionar "Enter". Se ele exibir uma janela que nos pergunta se queremos adicionar esse arquivo ao Git, vamos aceitar com um "Enter".
Com isso a estrutura básica abaixo será gerada.
package br.com.alura;
public class AppBanco {
}
Acessando o arquivo recém-criado, entre as chaves da classe AppBanco
, vamos digitar psvm
e pressionar "Tab" para transformar a classe em uma classe main
.
Entre as chaves do método main
, vamos começar criando uma entidade cliente, usando o var cliente
, que vai ser um new Cliente()
com o nome João.
Precisamos criar uma conta para João. Desceremos uma linha e faremos um var conta
, que será new Conta()
, passando entre parênteses o cliente
, que acabamos de criar, e um valor inicial para a conta. Quando ele cria a conta, pode escolher depositar algum valor. Então, à direita de cliente
, vamos informar um new BigDecimal()
, passando entre parênteses 150 reais, por exemplo.
package br.com.alura;
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
}
}
Queremos fazer dois saques ao mesmo tempo, utilizando essa conta. Para isso, vamos pressionar "Enter" duas vezes no final da linha da conta
e criar uma operação de saque, que vamos chamar de operacao
. Ela vai ser um new OperacaoSaque()
, passando entre parênteses a conta
e de novo o valor que queremos sacar.
Como queremos simular que as duas pessoas estão sacando o mesmo valor, para ver quais são os problemas que podem acontecer, vamos passar 150 reais novamente, por meio de um new BigDecimal("150")
.
package br.com.alura;
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
var operacao = new OperacaoSaque(conta, new BigDecimal("150"));
}
}
Instanciamos uma operação, mas precisamos executar esse saque. Primeiro, vamos supor que João sacará. Desceremos duas linha e comentaremos // saque João
, só para ver o que estará acontecendo no código. Desceremos uma linha e faremos um operacao.executa()
.
Mas, ao mesmo tempo, Maria recebeu o cartão adicional de João e também quer executar esse saque. Então, vamos querer fazer o saque de Maria. Vamos comentar //saque Maria
na linha de baixo, descer outra linha e fazer o operacao.executa()
, mais uma vez.
package br.com.alura;
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
var operacao = new OperacaoSaque(conta, new BigDecimal("150"));
//saque João
operacao.executa();
//saque Maria
operacao.executa();
}
}
Vamos ver o que vai acontecer. Se clicarmos no botão "Run 'AppBanco.java'", na parte direita no menu superior, vamos rodar o programa.
O terminal será aberto, no qual podemos ver que aconteceu o seguinte: iniciamos o saque, debitamos o valor da conta e o saldo atual passou a ser zero, porque tínhamos 150 reais e tiramos 150 reais. Depois, iniciamos outro saque, mas finalizamos logo depois, sem debitar nada.
Iniciando operação de saque.
Debitando valor da conta
Saldo atual: 0
Finalizando operação de saque.
Iniciando operação de saque.
Finalizando operação de saque.
Process finished with exit code 0
Por que isso ocorre? Se acessarmos a classe OperacaoSaque
, veremos que o saldo que tínhamos era zero. Quando tentamos tirar 150 de zero, não podemos fazer nada, porque senão o saldo vai ficar negativo.
Contudo, se voltarmos à classe AppBanco
, o que está sendo feito? Temos as variáveis sendo instanciadas, fazemos uma operacao.executa()
e, em seguida, fazemos uma operacao.executa()
para simular esses saques sequenciais de João e de Maria. É como se João sacasse e, logo depois, Maria sacasse.
Mas queremos que as duas coisas aconteçam ao mesmo tempo. Deve haver outra forma no Java de representar operações paralelas, isto é, que acontecem ao mesmo tempo.
Vamos ver isso logo em seguida, no próximo vídeo. Até lá!
Vimos que nossos saques estão ocorrendo de forma sequencial, mas queremos que eles ocorram de forma paralela, simulando isso no código. Você já deve ter percebido pelo nome do curso e pela introdução que, para trabalhar com processos paralelos em Java, utilizamos as threads.
Mas o que são e como utilizamos essas threads? São relacionadas ao Twitter ou ao aplicativo da Meta? Não!
Estamos falando das threads do Java, ou seja, uma classe que vamos utilizar, mapeadas diretamente para o sistema operacional. Veremos como em breve.
Para usar uma thread no código, voltaremos ao arquivo AppBanco
. Entre as chaves do método main()
, após a declaração da var operacao
, criaremos uma nova linha com "Enter".
Podemos fazer a thread do jeito mais tradicional, que é Thread thread = new Thread()
. Ao escrever isso, a IDE exibirá uma lista de vários tipos de construtores que podemos utilizar: o construtor padrão, outro que recebe um Runnable task
, outro no qual podemos passar o nome da thread, e vários outros.
Contudo, se escolhermos o new Thread()
com o construtor padrão, simplesmente criaremos uma thread na JVM, e não faremos mais nada com ela.
Estamos trabalhando com um banco, no qual criamos uma entidade cliente e uma conta. Depois, queremos simular que os saques sejam paralelos. Se queremos simular atividades paralelas, ou seja, tarefas diferentes, precisamos informar ao Java que temos uma thread e que queremos paralelizar nossas atividades. Em cada thread que criarmos, colocaremos tarefas diferentes.
Para colocar tarefas diferentes, precisamos passar um Runnable
com o estilo de construtor Thread(Runnable task)
. A tarefa (task) que queremos passar entre os parênteses será justamente a operacao
.
AppBanco.java
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
var operacao = new OperacaoSaque(conta, new BigDecimal("150"));
Thread thread = new Thread(operacao);
//saque João
operacao.executa();
//saque Maria
operacao.executa();
}
}
Contudo, ao acessar a classe OperacaoSaque
, veremos que é apenas uma classe básica do Java que declaramos como public class OperacaoSaque
. Precisamos transformá-la em um Runnable
.
Para fazer isso, precisaremos implementar a interface Runnable
. Logo após a declaração da classe, usaremos um implements Runnable
.
OperacaoSaque.java
public class OperacaoSaque implements Runnable{
// Código omitido
}
A interface Runnable
tem o método run()
. Vamos implementá-lo clicando no ícone de lâmpada vermelha, à esquerda dessa linha, e selecionamos a primeira opção, "Implement methods". Na nova janela exibida, o run()
já estará o run
selecionado, então pressionaremos o botão "OK".
public class OperacaoSaque implements Runnable{
// Código omitido
}
@Override
public void run() {
}
Entre as chaves do método run()
, precisaremos passar o que queremos que a thread faça quando for executada. Estamos executando o programa e queremos que algumas tarefas sejam executadas paralelamente.
No caso, queremos que os saques sejam feitos paralelamente. Como fazemos esse saque? Se olharmos a classe OperacaoSaque
, veremos que isso é feito pelo método executa()
. No método run()
, vamos chamar esse método executa()
.
@Override
public void run() {
executa();
}
Também poderíamos recortar o que está no corpo desse método e passar para o run()
, mas é mais fácil passarmos o método. Podemos fazer como preferirmos.
Escolhemos o que queremos fazer com a thread, então podemos voltar para o arquivo AppBanco
. Dentro dessa classe, criamos uma thread para um saque. Mas queremos duas threads para representar os dois saques. Para entender melhor, renomearemos a variável thread
para saqueJoao
. Teremos um saqueJoão
e um saqueMaria
, portanto, duplicaremos essa linha do saque com o "Ctrl+D" e renomearemos a segunda para saqueMaria
.
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
var operacao = new OperacaoSaque(conta, new BigDecimal("150"));
Thread saqueJoao = new Thread(operacao);
Thread saqueMaria = new Thread(operacao);
//saque João
operacao.executa();
//saque Maria
operacao.executa();
}
}
Criamos duas threads diferentes, mas apenas na memória. Precisamos "startar" (iniciá-las) de alguma forma.
Vamos usar o método start
abaixo da criação das threads, por meio de um saqueJoao.start
e de um saqueMaria.start
. Com isso, ele inicializará a thread.
Como as threads estão executando as tarefas, podemos comentar ou apagar as quatro linhas que criamos no final do método, com o saque de João e de Maria. Para comentar, selecionaremos as quatro linhas e pressionaremos "Ctrl+/".
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
var operacao = new OperacaoSaque(conta, new BigDecimal("150"));
Thread saqueJoao = new Thread(operacao);
Thread saqueMaria = new Thread(operacao);
saqueJoao.start();
saqueMaria.start();
// //saque João
// operacao.executa();
// //saque Maria
// operacao.executa();
// }
}
Vamos ver o que será executado. Pressiona "Shift+F10" para rodar o programa, em alternativa ao pressionamento do botão "Run".
Iniciando operação de saque.
Iniciando operação de saque.
Debitando valor da conta
Debitando valor da conta
Saldo atual: 0
Finalizando operação de saque.
Saldo atual: -150
Finalizando operação de saque.
Process finished with exit code 0
No terminal, podemos ver o que foi executado. Temos um "iniciando a operação de saque", "debitando o valor da conta", depois, ele mostra um saldo e finaliza. Depois, mostra outro saldo e finaliza.
Vemos um log e outro logo em seguida, depois o saldo e depois o débito. Isso indica que as duas operações foram executadas paralelamente. Podemos ver realmente as threads sendo executadas.
Uma thread é uma linha de execução. Toda vez que abrimos qualquer aplicativo do computador, uma thread é inicializada. Se temos o IntelliJ aberto, há uma thread sendo executada no sistema operacional. Se temos o Google Chrome aberto, também tem uma thread executando no sistema. Atualmente, temos a thread do IntelliJ, mas se executamos o Java, temos mais uma thread sendo aberta especificamente para ele.
Queremos programar situações específicas que são paralelas, portanto, queremos criar novas threads no programa. Para isso, usamos a classe Thread
que possibilita a criação de mais de uma thread no Java.
Vamos observar o que acontece quando estamos utilizando o Java e as threads. Temos um retângulo à esquerda denominado "Programa Java", e dele saem três setas denominadas "Thread 1", "Thread 2" e "Thread 3" sendo apontadas para três novas threads do sistema operacional à direita, a qual chamamos de "Thread SO 1", "Thread SO 2" e "Thread SO 3".
As threads que estão sendo criadas no programa Java são mapeadas diretamente para threads no sistema operacional.
Outra informação importante: toda vez que estamos executando um programa, já temos uma thread. Nesse caso, a "Thread 1" sempre será a main
(principal) e sempre vai existir. Posteriormente, podemos criar threads paralelamente no programa e ter novas threads.
Voltando ao código do arquivo AppBanco
, podemos ver isso claramente. Para conferirmos se novas threads realmente estão sendo criadas, vamos adicionar abaixo do start()
um método estático das threads chamado Thread.currentThread().getName()
.
Podemos querer imprimir esse método, que trará para nós o nome da thread atual que está sendo executada. Para isso adicionaremos o que está sendo feito na currentThread
entre os parênteses de um System.out.println()
.
AppBanco.java
public class AppBanco {
public static void main(String[] args) {
var cliente = new Cliente("João");
var conta = new Conta(cliente, new BigDecimal("150"));
var operacao = new OperacaoSaque(conta, new BigDecimal("150"));
Thread saqueJoao = new Thread(operacao);
Thread saqueMaria = new Thread(operacao);
saqueJoao.start();
saqueMaria.start();
System.out.println(Thread.currentThread().getName());
// //saque João
// operacao.executa();
// //saque Maria
// operacao.executa();
// }
}
O que está sendo feito no método main()
? Temos a entidade cliente, a conta e a operação sendo criados, depois, criamos novas threads.
Temos uma linha (thread) de execução main
sendo executada e que sempre existe, conforme mencionamos mais cedo. Mas existem também novas threads que estamos criando: a saqueJoao
e a saqueMaria
. Mas será que essas threads realmente estão sendo criadas?
Toda vez que executarmos a tarefa da operação, também imprimiremos a thread sendo executada usando o mesmo método currentThread().getName()
. Portanto, para verificar se as threads estão sendo criadas, copiaremos a linha System.out.println(Thread.currentThread().getName())
com "Ctrl+C", voltaremos ao arquivo OperacaoSaque
e colaremos entre as chaves do método run()
com "Ctrl+V", abaixo de executa()
.
OperacaoSaque.java
@Override
public void run() {
executa();
System.out.println(Thread.currentThread().getName());
}
Vamos executar novamente o programa usando "Shift+F10" para ver o que está sendo feito.
main
Iniciando operação de saque.
Iniciando operação de saque.
Debitando valor da conta
Debitando valor da conta
Saldo atual: -150
Finalizando operação de saque.
Saldo atual: 0
Finalizando operação de saque.
Thread-1
Thread-0
Process finished with exit code 0
No terminal, ele começa imprimindo main
, o nome da thread do método main
, a primeira e que sempre existe. Mas existem as threads que criamos também e que, nesse caso, estão sendo impressas como "Thread-1" e a "Thread-0". Isso significa que realmente conseguimos simular esses saques paralelos.
Esse paralelismo, ou seja, essas coisas que acontecem ao mesmo tempo, sempre vão existir no contexto do banco e em vários outros. Nesse sentido, as threads são essenciais para nós.
Se verificarmos o que está acontecendo no terminal, iniciamos a operação e debitamos, mas o saldo fica negativo. E essa não é uma regra válida no banco. Queremos que o saldo fique no máximo zerado, ele não pode ficar negativo.
Para tratar esse problema, precisamos lidar com os recursos das threads. Faremos isso logo em seguida.
O curso Java threads: aprenda a criar, gerenciar e aplicar com o Spring possui 155 minutos de vídeos, em um total de 54 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.