Sobes.tech
Middle

Comment organiser l'échange de données entre deux threads en Java?

sobes.tech IA

Réponse de l'IA

L'échange de données entre threads en Java peut être organisé de plusieurs manières:

  • Variables partagées avec synchronisation: Les threads utilisent des variables partagées, auxquelles l'accès est synchronisé à l'aide du mot-clé synchronized, de verrous (Lock de java.util.concurrent.locks) ou de variables atomiques (Atomic de java.util.concurrent.atomic).
  • Tuyaux de données (Streams liés): Les flux d'entrée/sortie liés (PipedInputStream et PipedOutputStream) permettent à un thread d'écrire des données et à un autre de les lire.
    // Utilisation des Streams liés
    try {
        PipedOutputStream pout = new PipedOutputStream();
        PipedInputStream pin = new PipedInputStream(pout);
    
        // Thread 1: écriture
        new Thread(() -> {
            try {
                pout.write("Bonjour du thread 1".getBytes());
                pout.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    
        // Thread 2: lecture
        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: L'utilisation de collections thread-safe, telles que BlockingQueue (par exemple, ArrayBlockingQueue, LinkedBlockingQueue), permet à un thread d'ajouter des éléments à la file d'attente et à un autre de les retirer.
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.LinkedBlockingQueue;
    
    // Utilisation de BlockingQueue
    BlockingQueue<String> queue = new LinkedBlockingQueue<>();
    
    // Producteur
    new Thread(() -> {
        try {
            queue.put("Message 1");
            queue.put("Message 2");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
    // Consommateur
    new Thread(() -> {
        try {
            System.out.println(queue.take());
            System.out.println(queue.take());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }).start();
    
  • Mécanismes wait(), notify(), notifyAll(): Utilisés pour synchroniser les threads basés sur des moniteurs d'objets. Un thread appelle wait() pour attendre une condition, un autre appelle notify() ou notifyAll() pour notifier les threads en attente d'un changement de condition.
    // Utilisation de wait/notify
    class Data {
        private String message;
        private boolean empty = true;
    
        public synchronized String read() {
            while (empty) {
                try {
                    wait(); // Attente de données
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = true;
            notifyAll(); // Notification de la disponibilité pour écrire
            return message;
        }
    
        public synchronized void write(String message) {
            while (!empty) {
                try {
                    wait(); // Attente que les données précédentes soient lues
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            empty = false;
            this.message = message;
            notifyAll(); // Notification de nouvelles données
        }
    }
    
  • CompletableFuture: Permet de créer des calculs asynchrones et de les combiner, facilitant l'échange de données via des valeurs de retour et des gestionnaires de fin.
  • Exchanger: Permet à deux threads d'échanger des objets à un point de synchronisation.

Le choix d'une méthode spécifique dépend de la nature de la tâche, du niveau de synchronisation requis, des performances et de la complexité de l'implémentation.