Sobes.tech
Middle

Wie kann man den Datenaustausch zwischen zwei Threads in Java organisieren?

sobes.tech KI

Antwort von AI

Der Datenaustausch zwischen Threads in Java kann auf folgende Weisen organisiert werden:

  • Gemeinsame Variablen mit Synchronisation: Threads verwenden gemeinsame Variablen, auf die mit dem Schlüsselwort synchronized, Sperren (Lock aus java.util.concurrent.locks) oder atomaren Variablen (Atomic aus java.util.concurrent.atomic) zugegriffen wird.
  • Datenpipelines (Verkettete Streams): Verbundene Ein- und Ausgabeströme (PipedInputStream und PipedOutputStream) ermöglichen es einem Thread, Daten zu schreiben, und einem anderen, sie zu lesen.
    // Verwendung von piped streams
    try {
        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream(pout);
    
        // Thread 1: Schreiben
        new Thread(() -> {
            try {
                pout.write("Hallo vom Thread 1".getBytes());
                pout.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    
        // Thread 2: Lesen
        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();
    }
    
  • Queues: Die Verwendung von thread-sicheren Sammlungen wie BlockingQueue (z.B. ArrayBlockingQueue, LinkedBlockingQueue) ermöglicht es einem Thread, Elemente in die Warteschlange zu legen, und einem anderen, sie zu entnehmen.
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    // Verwendung von BlockingQueue
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Hersteller
    new Thread(() -> {
        try {
            queue.put("Nachricht 1");
            queue.put("Nachricht 2");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
    // Verbraucher
    new Thread(() -> {
        try {
            System.out.println(queue.take());
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
  • Mechanismen wait(), notify(), notifyAll(): Werden verwendet, um Threads anhand von Objektmonitoren zu synchronisieren. Ein Thread ruft wait() auf, um auf eine Bedingung zu warten, ein anderer ruft notify() oder notifyAll() auf, um wartende Threads über eine Änderung der Bedingung zu benachrichtigen.
    // Verwendung von wait/notify
    class Data {
        private String message;
        private boolean empty = true;
    
        public synchronized String read() {
            while (empty) {
                try {
                    wait(); // Warten auf Daten
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = true;
            notifyAll(); // Benachrichtigen, dass Schreiben möglich ist
            return message;
        }
    
        public synchronized void write(String message) {
            while (!empty) {
                try {
                    wait(); // Warten, bis die vorherigen Daten gelesen werden
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = false;
            this.message = message;
            notifyAll(); // Benachrichtigung über neue Daten
        }
    }
    
  • CompletableFuture: Ermöglicht das Erstellen asynchroner Berechnungen und deren Kombination, wobei Daten durch Rückgabewerte und Abschluss-Handler ausgetauscht werden.
  • Exchanger: Ermöglicht es zwei Threads, Objekte an einem Synchronisationspunkt auszutauschen.

Die Wahl einer bestimmten Methode hängt von der Art der Aufgabe, dem erforderlichen Synchronisationsgrad, der Leistung und der Komplexität der Implementierung ab.