Sobes.tech
Middle

How can data exchange be organized between two threads in Java?

sobes.tech AI

Answer from AI

Data exchange between threads in Java can be organized using the following methods:

  • Shared variables with synchronization: Threads use shared variables, access to which is synchronized using the synchronized keyword, locks (Lock from java.util.concurrent.locks), or atomic variables (Atomic from java.util.concurrent.atomic).
  • Data pipelines (Piped Streams): Connected input/output streams (PipedInputStream and PipedOutputStream) allow one thread to write data and another to read it.
    // Using Piped Streams
    try {
        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream(pout);
    
        // Thread 1: writing
        new Thread(() -> {
            try {
                pout.write("Hello from thread 1".getBytes());
                pout.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    
        // Thread 2: reading
        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: Using thread-safe collections such as BlockingQueue (e.g., ArrayBlockingQueue, LinkedBlockingQueue) allows one thread to put elements into the queue and another to retrieve them.
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    // Using BlockingQueue
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Producer
    new Thread(() -> {
        try {
            queue.put("Message 1");
            queue.put("Message 2");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
    // Consumer
    new Thread(() -> {
        try {
            System.out.println(queue.take());
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
  • wait(), notify(), notifyAll(): Used for thread synchronization based on object monitors. One thread calls wait() to wait for a condition, another calls notify() or notifyAll() to notify waiting threads about a change in the condition.
    // Using wait/notify
    class Data {
        private String message;
        private boolean empty = true;
    
        public synchronized String read() {
            while (empty) {
                try {
                    wait(); // Waiting for data
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = true;
            notifyAll(); // Notify about possible writing
            return message;
        }
    
        public synchronized void write(String message) {
            while (!empty) {
                try {
                    wait(); // Waiting for previous data to be read
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = false;
            this.message = message;
            notifyAll(); // Notify about new data
        }
    }
    
  • CompletableFuture: Allows creating asynchronous computations and combining their results, enabling data exchange through return values and completion handlers.
  • Exchanger: Allows two threads to exchange objects at a synchronization point.

The choice of a specific method depends on the task's specifics, required synchronization level, performance, and implementation complexity.