1.1 Introdução ao Multithreading
É uma técnica que permite a execução concorrente de múltiplos threads, seja em software ou hardware. Sistemas com suporte a multithreading podem executar vários threads simultaneamente, melhorando o desempenho geral.
1.2 Concorrência vs Paralelismo
- Paralelismo: Múltiplas instruções sendo executadas simultaneamente em múltiplos CPUs.
- Concorrência: Múltiplas instruções sendo executadas alternadamente em um único CPU.
1.3 Processos e Threads
- Processo: Um programa em execução
- Independência: Cada processo é uma unidade independente de execução e alocação de recursos.
- Dinamicismo: Processos têm ciclo de vida (criação, execução, término).
- Concorrência: Vários processos podem executar simultaneamente.
- Thread: Um fluxo de controle único dentro de um processo
- Thread única: Processo com um único fluxo de execução.
- Múltiplas threads: Processo com múltiplos fluxos de execução.
1.4 Implementação de Multithreading: Herança da Classe Thread
Métodos Principais
| Método |
Descrição |
| void run() |
Chamado após o thread iniciar, contém o código a ser executado |
| void start() |
Inicia a execução do thread, invoca o método run() pela JVM |
Passos de Implementação
- Definir uma classe que herda de Thread
- Sobrescrever o método run()
- Criar instância da classe
- Iniciar o thread
Exemplo de Código
public class MinhaThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println("Thread " + Thread.currentThread().getName() + ": " + i);
}
}
}
public class ExemploThread {
public static void main(String[] args) {
MinhaThread t1 = new MinhaThread();
MinhaThread t2 = new MinhaThread();
t1.start();
t2.start();
}
}
1.5 Implementação de Multithreading: Interface Runnable
Construtores da Classe Thread
| Construtor |
Descrição |
| Thread(Runnable target) |
Cria um novo objeto Thread |
| Thread(Runnable target, String name) |
Cria um novo objeto Thread com nome específico |
Passos de Implementação
- Definir uma classe que implementa Runnable
- Sobrescrever o método run()
- Criar instância da classe
- Criar objeto Thread passando a instância
- Iniciar o thread
Exemplo de Código
public class MinhaTarefa implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
public class ExemploRunnable {
public static void main(String[] args) {
MinhaTarefa tarefa = new MinhaTarefa();
Thread t1 = new Thread(tarefa, "Processador 1");
Thread t2 = new Thread(tarefa, "Processador 2");
t1.start();
t2.start();
}
}
1.6 Implementação de Multithreading: Interface Callable
Métodos Principais
| Método |
Descrição |
| V call() |
Executa a tarefa e retorna um resultado |
| FutureTask(Callable callable) |
Cria um FutureTask que executa o Callable |
| V get() |
Espera o término e obtém o resultado |
Passos de Implementação
- Definir uma classe que implementa Callable
- Sobrescrever o método call()
- Criar instância da classe
- Criar objeto FutureTask
- Criar objeto Thread com o FutureTask
- Iniciar o thread
- Obter o resultado com get()
Exemplo de Código
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MinhaTarefaComResultado implements Callable<string> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("Processando item " + i);
}
return "Tarefa concluída com sucesso";
}
}
public class ExemploCallable {
public static void main(String[] args) {
MinhaTarefaComResultado tarefa = new MinhaTarefaComResultado();
FutureTask<string> futuro = new FutureTask<>(tarefa);
Thread t1 = new Thread(futuro);
t1.start();
try {
String resultado = futuro.get();
System.out.println("Resultado: " + resultado);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}</string></string>
1.7 Comparação dos Métodos de Implementação
| Abordagem |
Vantagens |
Desvantagens |
| Runnable/Callable |
Maior flexibilidade de herança |
Mais complexo, não usa diretamente métodos de Thread |
| Herança de Thread |
Simples de implementar |
Restringe herança única |
1.8 Nome de Threads
Métodos Principais
| Método |
Descrição |
| void setName(String name) |
Define o nome do thread |
| String getName() |
Retorna o nome do thread |
| Thread currentThread() |
Retorna a referência do thread atual |
Exemplo de Código
public class ThreadComNome extends Thread {
public ThreadComNome(String nome) {
super(nome);
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(getName() + ": " + i);
}
}
}
public class ExemploNomeThread {
public static void main(String[] args) {
ThreadComNome t1 = new ThreadComNome("Servidor A");
ThreadComNome t2 = new ThreadComNome("Servidor B");
t1.start();
t2.start();
System.out.println("Thread principal: " + Thread.currentThread().getName());
}
}
1.9 Espera de Threads (Sleep)
Método Principal
| Método |
Descrição |
| static void sleep(long millis) |
Pausa a execução do thread por milissegundos |
Exemplo de Código
public class TarefaComEspera implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000); // Espera 1 segundo
System.out.println(Thread.currentThread().getName() + ": Contagem " + i);
} catch (InterruptedException e) {
System.out.println("Thread interrompida");
}
}
}
}
public class ExemploSleep {
public static void main(String[] args) {
TarefaComEspera tarefa = new TarefaComEspera();
Thread t1 = new Thread(tarefa, "Timer 1");
Thread t2 = new Thread(tarefa, "Timer 2");
t1.start();
t2.start();
}
}
1.10 Prioridade de Threads
Agendamento de Threads
- Modelo de tempo compartilhado: Todos os threads usam o CPU igualmente
- Modelo de preempção: Threads com prioridade maior usam mais o CPU
Java usa modelo de preempção, mas a execução é devido à natureza aleatória do agendamento.
Métodos de Prioridade
| Método |
Descrição |
| int getPriority() |
Retorna a prioridade do thread |
| void setPriority(int newPriority) |
Define a prioridade (1-10, padrão 5) |
Exemplo de Código
public class TarefaPrioridade implements Runnable {
@Override
public void run() {
System.out.println("Executando " + Thread.currentThread().getName()
+ " com prioridade " + Thread.currentThread().getPriority());
}
}
public class ExemploPrioridade {
public static void main(String[] args) {
TarefaPrioridade tarefa = new TarefaPrioridade();
Thread t1 = new Thread(tarefa, "Alta Prioridade");
t1.setPriority(Thread.MAX_PRIORITY); // 10
Thread t2 = new Thread(tarefa, "Baixa Prioridade");
t2.setPriority(Thread.MIN_PRIORITY); // 1
t1.start();
t2.start();
}
}
1.11 Threads de Demon (Daemon)
Método Principal
| Método |
Descrição |
| void setDaemon(boolean on) |
Marca o thread como de demon |
Exemplo de Código
public class ThreadPrincipal extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ": " + i);
}
}
}
public class ThreadDemonio extends Thread {
@Override
public void run() {
while (true) {
System.out.println(getName() + ": Em segundo plano");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ExemploDemonio {
public static void main(String[] args) {
ThreadPrincipal principal = new ThreadPrincipal();
ThreadDemonio demonio = new ThreadDemonio();
demonio.setDaemon(true); // Define como thread de demon
principal.start();
demonio.start();
try {
principal.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread principal terminada");
}
}
- Sincronização de Threads
2.1 Problema de Venda de Ingressos
Requisito
Cinema com 100 ingressos e 3 guichês para venda. Simular o processo de venda.
Implementação
public class VendaIngressos implements Runnable {
private int ingressos = 100;
@Override
public void run() {
while (true) {
if (ingressos <= 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ingressos--;
System.out.println(Thread.currentThread().getName() + " vendeu um ingresso. Restam: " + ingressos);
}
}
}
}
public class ExemploVenda {
public static void main(String[] args) {
VendaIngressos venda = new VendaIngressos();
Thread t1 = new Thread(venda, "Guichê 1");
Thread t2 = new Thread(venda, "Guichê 2");
Thread t3 = new Thread(venda, "Guichê 3");
t1.start();
t2.start();
t3.start();
}
}
2.2 Problemas no Exemplo
- Mesmo ingresso sendo vendido múltiplas vezes
- Números negativos de ingressos
Causa: Aleatoriedade na execução dos threads levando a condições de corrida.
2.3 Bloco Sincronizado
Condições para Problemas
- Ambiente multithread
- Dados compartilhados
- Múltiplas instruções operando os dados
Solução
Usar blocos sincronizados para garantir que apenas um thread execute a crítica seção por vez:
Exemplo de Código
public class VendaIngressosSegura implements Runnable {
private int ingressos = 100;
private final Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (ingressos <= 0) {
break;
} else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ingressos--;
System.out.println(Thread.currentThread().getName() + " vendeu um ingresso. Restam: " + ingressos);
}
}
}
}
}
2.4 Métodos Sincronizados
Sintaxe
modificador synchronized tipoRetorno nomeMétodo(parâmetros) {
// Código sincronizado
}
Objeto de Lock
- Método de instância: this
- Método estático: NomeDaClasse.class
Exemplo de Código
public class GerenciadorIngressos {
private static int ingressos = 100;
public static synchronized boolean venderIngresso() {
if (ingressos <= 0) {
return false;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ingressos--;
System.out.println(Thread.currentThread().getName() + " vendeu um ingresso. Restam: " + ingressos);
return true;
}
}
public class Vendedor implements Runnable {
@Override
public void run() {
while (GerenciadorIngressos.venderIngresso()) {
// Continuar vendendo
}
}
}
2.5 Lock Interface
Vantagens
Mais controle sobre o lock em comparação com synchronized.
Métodos Principais
| Método |
Descrição |
| void lock() |
Adquire o lock |
| void unlock() |
Liberar o lock |
Exemplo de Código
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SistemaReserva {
private int assentos = 100;
private final Lock lock = new ReentrantLock();
public boolean reservarAssento() {
lock.lock();
try {
if (assentos <= 0) {
return false;
}
Thread.sleep(100);
assentos--;
System.out.println(Thread.currentThread().getName() + " reservou um assento. Restam: " + assentos);
return true;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
} finally {
lock.unlock();
}
}
}
public class Cliente implements Runnable {
private SistemaReserva sistema;
public Cliente(SistemaReserva sistema) {
this.sistema = sistema;
}
@Override
public void run() {
while (sistema.reservarAssento()) {
// Tentar reservar novamente
}
}
}
2.6 Deadlock
Conceito
Situação onde dois ou mais threads ficam bloqueados permanentemente, cada um aguardando o outro liberar um recurso.
Condições
- Recursos exclusivos
- Sincronização aninhada
Exemplo de Código
public class ExemploDeadlock {
private static final Object RECURSO_A = new Object();
private static final Object RECURSO_B = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (RECURSO_A) {
System.out.println("Thread 1: Adquiriu Recurso A");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (RECURSO_B) {
System.out.println("Thread 1: Adquiriu Recurso B");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (RECURSO_B) {
System.out.println("Thread 2: Adquiriu Recurso B");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (RECURSO_A) {
System.out.println("Thread 2: Adquiriu Recurso A");
}
}
});
t1.start();
t2.start();
}
}
- Padrão Produtor-Consumidor
3.1 Visão Geral
Padrão clássico de colaboração entre threads onde:
- Produtor: Cria dados e os coloca em uma área compartilhada
- Consumidor: Consome dados da área compartilhada
Métodos de Espera e Notificação
| Método |
Descrição |
| void wait() |
Faz o thread esperar até ser notificado |
| void notify() |
Acorda um thread em espera |
| void notifyAll() |
Acorda todos os threads em espera |
3.2 Exemplo Básico
Código
public class BufferCompartilhado {
private int dado = -1;
private boolean temDado = false;
public synchronized void put(int valor) {
while (temDado) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
this.dado = valor;
this.temDado = true;
notifyAll();
}
public synchronized int get() {
while (!temDado) {
try {
wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
int valor = this.dado;
this.temDado = false;
notifyAll();
return valor;
}
}
public class Produtor implements Runnable {
private BufferCompartilhado buffer;
public Produtor(BufferCompartilhado buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
buffer.put(i);
System.out.println("Produziu: " + i);
try {
Thread.sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public class Consumidor implements Runnable {
private BufferCompartilhado buffer;
public Consumidor(BufferCompartilhado buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int valor = buffer.get();
System.out.println("Consumiu: " + valor);
try {
Thread.sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public class ExemploProdutorConsumidor {
public static void main(String[] args) {
BufferCompartilhado buffer = new BufferCompartilhado();
Thread produtorThread = new Thread(new Produtor(buffer), "Produtor");
Thread consumidorThread = new Thread(new Consumidor(buffer), "Consumidor");
produtorThread.start();
consumidorThread.start();
}
}
3.3 Filas de Bloqueio (BlockingQueue)
Vantagens
Implementação pronta do padrão produtor-consumidor com sincronização embutida.
Tipos Comuns
- ArrayBlockingQueue: Baseada em array, com capacidade fixa
- LinkedBlockingQueue: Baseada em lista, capacidade teoricamente ilimitada
Métodos Principais
| Método |
Descrição |
| put(E e) |
Insere elemento, bloqueia se cheio |
| take() |
Remove elemento, bloqueia se vazio |
Exemplo de Código
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class SistemaFila {
private BlockingQueue<string> fila;
public SistemaFila(int capacidade) {
this.fila = new ArrayBlockingQueue<>(capacidade);
}
public void adicionarTarefa(String tarefa) throws InterruptedException {
fila.put(tarefa);
System.out.println("Tarefa adicionada: " + tarefa);
}
public String processarTarefa() throws InterruptedException {
String tarefa = fila.take();
System.out.println("Processando: " + tarefa);
return tarefa;
}
}
public class Trabalhador implements Runnable {
private SistemaFila sistema;
public Trabalhador(SistemaFila sistema) {
this.sistema = sistema;
}
@Override
public void run() {
try {
while (true) {
String tarefa = sistema.processarTarefa();
// Simular processamento
Thread.sleep((long)(Math.random() * 1000));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class GerenciadorTarefas implements Runnable {
private SistemaFila sistema;
public GerenciadorTarefas(SistemaFila sistema) {
this.sistema = sistema;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
sistema.adicionarTarefa("Tarefa-" + i);
Thread.sleep((long)(Math.random() * 500));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class ExemploBlockingQueue {
public static void main(String[] args) {
SistemaFila sistema = new SistemaFila(5);
Thread gerenciador = new Thread(new GerenciadorTarefas(sistema), "Gerenciador");
Thread trabalhador1 = new Thread(new Trabalhador(sistema), "Trabalhador 1");
Thread trabalhador2 = new Thread(new Trabalhador(sistema), "Trabalhador 2");
gerenciador.start();
trabalhador1.start();
trabalhador2.start();
}
}</string>