Sobes.tech
Middle

Ako zorganizovať výmenu dát medzi dvoma vláknami v Jave?

sobes.tech AI

Odpoveď od AI

Spôsoby výmeny údajov medzi vláknami v Jave je možné organizovať nasledovne:

  • Zdieľané premenné so synchronizáciou: Vlákna používajú zdieľané premenné, ku ktorým pristupujú synchronizovane pomocou kľúčového slova synchronized, zámkov (Lock z java.util.concurrent.locks) alebo atómových premenných (Atomic z java.util.concurrent.atomic).
  • Dátové potrubia (Piped Streams): Prepojené vstupné/výstupné prúdy (PipedInputStream a PipedOutputStream) umožňujú jednému vláknu zapisovať údaje, zatiaľ čo iné ich číta.
    // Použitie 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: čítanie
        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žitie bezpečných kolekcií pre vlákna, ako je BlockingQueue (napríklad, ArrayBlockingQueue, LinkedBlockingQueue), umožňuje jednému vláknu vkladať prvky do fronty, zatiaľ čo iné ich odoberá.
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    // Použitie BlockingQueue
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Výrobca
    new Thread(() -> {
        try {
            queue.put("Správa 1");
            queue.put("Správa 2");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
    // Spotrebiteľ
    new Thread(() -> {
        try {
            System.out.println(queue.take());
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
  • Mechanizmy wait(), notify(), notifyAll(): Používajú sa na synchronizáciu vlákien na základe monitorov objektov. Jedno vlákno volá wait() na čakanie na podmienku, iné volá notify() alebo notifyAll() na oznámenie čakajúcim vláknam o zmene podmienky.
    // Použitie wait/notify
    class Data {
        private String message;
        private boolean empty = true;
    
        public synchronized String read() {
            while (empty) {
                try {
                    wait(); // Čakanie na údaje
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = true;
            notifyAll(); // Oznámenie o možnosti zápisu
            return message;
        }
    
        public synchronized void write(String message) {
            while (!empty) {
                try {
                    wait(); // Čakanie, kým sa prečítajú predchádzajúce údaje
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = false;
            this.message = message;
            notifyAll(); // Oznámenie o nových údajoch
        }
    }
    
  • CompletableFuture: Umožňuje vytvárať asynchrónne výpočty a ich výsledky spájať, zabezpečujúc výmenu údajov prostredníctvom vracaných hodnôt a handlerov ukončenia.
  • Exchanger: Umožňuje dvom vláknam vymeniť objekty v bode synchronizácie.

Výber konkrétnej metódy závisí od špecifiká úlohy, požadovanej úrovne synchronizácie, výkonu a zložitosti implementácie.