Sobes.tech
Middle

Como organizar a troca de dados entre dois threads em Java?

sobes.tech IA

Resposta da IA

A troca de dados entre threads em Java pode ser organizada das seguintes formas:

  • Variáveis partilhadas com sincronização: Os threads usam variáveis partilhadas, cujo acesso é sincronizado usando a palavra-chave synchronized, bloqueios (Lock de java.util.concurrent.locks) ou variáveis atómicas (Atomic de java.util.concurrent.atomic).
  • Túneis de dados (Streams ligados): Os fluxos de entrada/saída ligados (PipedInputStream e PipedOutputStream) permitem que um thread escreva dados e outro os leia.
    // Uso de Streams ligados
    try {
        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream(pout);
    
        // Thread 1: escrita
        new Thread(() -> {
            try {
                pout.write("Olá do thread 1".getBytes());
                pout.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    
        // Thread 2: leitura
        new Thread(() -> {
            try {
                int data;
                while ((data = pin.read()) != -1) {
                    System.out.print((char) data);
                }
                pin.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
  • Filas (Queues): O uso de coleções seguras para threads, como BlockingQueue (por exemplo, ArrayBlockingQueue, LinkedBlockingQueue), permite que um thread coloque elementos na fila e outro os retire.
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    // Uso de BlockingQueue
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Produtor
    new Thread(() -> {
        try {
            queue.put("Mensagem 1");
            queue.put("Mensagem 2");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
    // Consumidor
    new Thread(() -> {
        try {
            System.out.println(queue.take());
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
  • Mecanismos wait(), notify(), notifyAll(): São usados para sincronizar threads com base em monitores de objetos. Uma thread chama wait() para esperar por uma condição, outra chama notify() ou notifyAll() para notificar threads em espera sobre uma mudança de condição.
    // Uso de wait/notify
    class Data {
        private String message;
        private boolean empty = true;
    
        public synchronized String read() {
            while (empty) {
                try {
                    wait(); // Espera por dados
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = true;
            notifyAll(); // Notifica que pode escrever
            return message;
        }
    
        public synchronized void write(String message) {
            while (!empty) {
                try {
                    wait(); // Espera que os dados anteriores sejam lidos
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = false;
            this.message = message;
            notifyAll(); // Notifica novos dados
        }
    }
    
  • CompletableFuture: Permite criar cálculos assíncronos e combiná-los, facilitando a troca de dados através de valores de retorno e manipuladores de finalização.
  • Exchanger: Permite que dois threads troquem objetos num ponto de sincronização.

A escolha de um método específico depende da natureza da tarefa, do nível de sincronização necessário, do desempenho e da complexidade de implementação.