Sincronização da Troca de Dados entre Threads em Java: A Classe Exchanger
A classe Exchanger
, parte do pacote java.util.concurrent
, oferece um mecanismo de sincronização projetado especificamente para quando um par de threads precisa trocar dados entre si. Ela cria um ponto de encontro, onde duas threads podem trocar um objeto de forma segura, garantindo que a troca ocorra como uma operação única e completa, sem o risco de estados intermediários ou perda de dados.
A classe é genérica (Exchanger<V>
), permitindo a troca de qualquer tipo de objeto. A troca é realizada pelo seu principal método, exchange()
:
V exchange(V x)
: Aguarda que outra thread chegue a este ponto de troca. Quando a segunda thread chega, o objetox
é transferido para ela, e o método retorna o objeto que a outra thread forneceu.V exchange(V x, long timeout, TimeUnit unit)
: Funciona da mesma forma, mas lança umaTimeoutException
se a outra thread não chegar dentro do tempo de espera especificado.
O funcionamento é simples: a primeira thread a invocar exchange()
é bloqueada e fica em estado de espera. Quando a segunda thread invoca exchange()
, a troca de dados ocorre instantaneamente, e ambas as threads são desbloqueadas para continuar suas execuções. Se uma terceira thread chamar exchange()
, ela também ficará bloqueada, esperando por uma quarta thread para formar um novo par de troca.
Exemplo de Uso
Vamos criar duas threads que trocarão mensagens do tipo String
em um ponto de encontro.
import java.util.concurrent.Exchanger;
public class Program {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
// Cria e inicia duas threads que usarão o mesmo Exchanger
new Thread(new TaskA(exchanger)).start();
new Thread(new TaskB(exchanger)).start();
}
}
class TaskA implements Runnable {
private Exchanger<String> exchanger;
TaskA(Exchanger<String> ex) {
this.exchanger = ex;
}
public void run() {
String message = "Dados da Tarefa A";
try {
System.out.println("TaskA chegou ao ponto de troca e está esperando...");
// Troca a mensagem e recebe a mensagem da TaskB
String receivedMessage = exchanger.exchange(message);
System.out.println("TaskA recebeu: " + receivedMessage);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("TaskA foi interrompida.");
}
}
}
class TaskB implements Runnable {
private Exchanger<String> exchanger;
TaskB(Exchanger<String> ex) {
this.exchanger = ex;
}
public void run() {
String message = "Informação da Tarefa B";
try {
System.out.println("TaskB está se aproximando do ponto de troca...");
Thread.sleep(1000); // Simula algum trabalho
// Troca a mensagem e recebe a mensagem da TaskA
String receivedMessage = exchanger.exchange(message);
System.out.println("TaskB recebeu: " + receivedMessage);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("TaskB foi interrompida.");
}
}
}
Neste exemplo, TaskA
chega primeiro ao exchanger.exchange()
e fica bloqueada. TaskB
simula um trabalho por um segundo e então também chega ao ponto de troca. Nesse momento, a troca acontece: TaskA
recebe "Informação da Tarefa B"
e TaskB
recebe "Dados da Tarefa A"
.
O valor de retorno do método exchange()
pode ser ignorado se o objetivo for apenas sincronizar as duas threads em um ponto específico, sem a necessidade de usar o dado recebido.
A saída da execução será parecida com esta (a ordem das duas primeiras linhas pode variar):
TaskA chegou ao ponto de troca e está esperando... TaskB está se aproximando do ponto de troca... TaskB recebeu: Dados da Tarefa A TaskA recebeu: Informação da Tarefa B
Resumo
- O
Exchanger
é projetado para sincronizar um par de threads em um ponto de troca de dados. - A chamada ao método
exchange()
é bloqueante: a primeira thread a chegar espera pela segunda. - No momento da troca, os objetos são trocados atomicamente, e ambas as threads são liberadas.
- É ideal para padrões como double buffering, onde duas threads trocam buffers de dados (um para escrita, outro para leitura) de forma eficiente.