Sobes.tech
Middle

Jak lze zorganizovat výměnu dat mezi dvěma vlákny v Java?

sobes.tech AI

Odpověď od AI

Způsoby výměny dat mezi vlákny v Javě lze organizovat následujícími způsoby:

  • Sdílené proměnné se synchronizací: Vlákna používají sdílené proměnné, ke kterým přistupují synchronizovaně pomocí klíčového slova synchronized, zámků (Lock z java.util.concurrent.locks) nebo atomických proměnných (Atomic z java.util.concurrent.atomic).
  • Datové pipy (Piped Streams): Propojené vstupní/výstupní proudy (PipedInputStream a PipedOutputStream) umožňují jednomu vláknu zapisovat data, zatímco jiné je čte.
    // Použití Piped Streams
    try {
        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream(pout);
    
        // Vlákno 1: zápis
        new Thread(() -> {
            try {
                pout.write("Ahoj z vlákna 1".getBytes());
                pout.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    
        // Vlákno 2: čtení
        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();
    }
    
  • Fronty (Queues): Použití bezpečných kolekcí pro vlákna, jako je BlockingQueue (například ArrayBlockingQueue, LinkedBlockingQueue), umožňuje jednomu vláknu vkládat prvky do fronty a jinému je odebírat.
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    // Použití BlockingQueue
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Výrobce
    new Thread(() -> {
        try {
            queue.put("Zpráva 1");
            queue.put("Zpráva 2");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
    // Spotřebitel
    new Thread(() -> {
        try {
            System.out.println(queue.take());
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
  • Mechanismy wait(), notify(), notifyAll(): Používají se pro synchronizaci vláken na základě monitorů objektů. Jedno vlákno volá wait() pro čekání na podmínku, jiné volá notify() nebo notifyAll() pro oznámení čekajícím vláknům o změně podmínky.
    // Použití wait/notify
    class Data {
        private String message;
        private boolean empty = true;
    
        public synchronized String read() {
            while (empty) {
                try {
                    wait(); // Čekání na data
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = true;
            notifyAll(); // Oznámení o možnosti zápisu
            return message;
        }
    
        public synchronized void write(String message) {
            while (!empty) {
                try {
                    wait(); // Čekání, dokud se nepřečtou předchozí data
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = false;
            this.message = message;
            notifyAll(); // Oznámení o nových datech
        }
    }
    
  • CompletableFuture: Umožňuje vytvářet asynchronní výpočty a spojovat jejich výsledky, zajišťující výměnu dat prostřednictvím vrácených hodnot a handlerů dokončení.
  • Exchanger: Umožňuje dvěma vláknům vyměnit objekty v bodě synchronizace.

Výběr konkrétní metody závisí na specifikách úkolu, požadované úrovni synchronizace, výkonu a složitosti implementace.