web-dev-qa-db-fra.com

Fils de producteur / consommateur utilisant une file d'attente

J'aimerais créer une sorte de Producer/Consumer application de filetage. Mais je ne sais pas quel est le meilleur moyen d'implémenter une file d'attente entre les deux.

Donc, j'en ai deux avec deux idées (qui pourraient toutes les deux être fausses). J'aimerais savoir ce qui serait le mieux et s'ils étaient tous les deux nuls, quel serait le meilleur moyen de mettre en œuvre la file d'attente. Ce qui me préoccupe, c'est principalement la mise en œuvre de la file d'attente dans ces exemples. J'étends une classe de file d'attente qui est une classe interne et est thread-safe. Ci-dessous deux exemples avec 4 classes chacun.

Classe principale

public class SomeApp
{
    private Consumer consumer;
    private Producer producer;

    public static void main (String args[])
    {
        consumer = new Consumer();
        producer = new Producer();
    }
} 

Classe de consommation

public class Consumer implements Runnable
{
    public Consumer()
    {
        Thread consumer = new Thread(this);
        consumer.start();
    }

    public void run()
    {
        while(true)
        {
            //get an object off the queue
            Object object = QueueHandler.dequeue();
            //do some stuff with the object
        }
    }
}

Classe de producteur

public class Producer implements Runnable
{
    public Producer()
    {
        Thread producer = new Thread(this);
        producer.start();
    }

    public void run()
    {
        while(true)
        {
            //add to the queue some sort of unique object
            QueueHandler.enqueue(new Object());
        }
    }
}

Classe de file d'attente

public class QueueHandler
{
    //This Queue class is a thread safe (written in house) class
    public static Queue<Object> readQ = new Queue<Object>(100);

    public static void enqueue(Object object)
    {
        //do some stuff
        readQ.add(object);
    }

    public static Object dequeue()
    {
        //do some stuff
        return readQ.get();
    }
}

OU

Classe principale

public class SomeApp
{
    Queue<Object> readQ;
    private Consumer consumer;
    private Producer producer;

    public static void main (String args[])
    {
        readQ = new Queue<Object>(100);
        consumer = new Consumer(readQ);
        producer = new Producer(readQ);
    }
} 

Classe de consommation

public class Consumer implements Runnable
{
    Queue<Object> queue;

    public Consumer(Queue<Object> readQ)
    {
        queue = readQ;
        Thread consumer = new Thread(this);
        consumer.start();
    }

    public void run()
    {
        while(true)
        {
            //get an object off the queue
            Object object = queue.dequeue();
            //do some stuff with the object
        }
    }
}

Classe de producteur

public class Producer implements Runnable
{
    Queue<Object> queue;

    public Producer(Queue<Object> readQ)
    {
        queue = readQ;
        Thread producer = new Thread(this);
        producer.start();
    }

    public void run()
    {

        while(true)
        {
            //add to the queue some sort of unique object
            queue.enqueue(new Object());
        }
    }
}

Classe de file d'attente

//the extended Queue class is a thread safe (written in house) class
public class QueueHandler extends Queue<Object>
{    
    public QueueHandler(int size)
    {
        super(size); //All I'm thinking about now is McDonalds.
    }

    public void enqueue(Object object)
    {
        //do some stuff
        readQ.add();
    }

    public Object dequeue()
    {
        //do some stuff
        return readQ.get();
    }
}

Et aller!

56
Gareth

Java 5+ a tous les outils dont vous avez besoin pour ce genre de chose. Vous voudrez:

  1. Mettez tous vos producteurs en un ExecutorService ;
  2. Mettez tous vos consommateurs dans un autre ExecutorService;
  3. Si nécessaire, communiquez entre les deux en utilisant un BlockingQueue .

Je dis "si nécessaire" pour (3) car, d’après mon expérience, c’est une étape inutile. Tout ce que vous faites est de soumettre de nouvelles tâches au service client exécuteur. Alors:

final ExecutorService producers = Executors.newFixedThreadPool(100);
final ExecutorService consumers = Executors.newFixedThreadPool(100);
while (/* has more work */) {
  producers.submit(...);
}
producers.shutdown();
producers.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
consumers.shutdown();
consumers.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

Donc, le producers se soumet directement à consumers.

76
cletus

OK, comme d'autres le notent, la meilleure chose à faire est d'utiliser le paquet Java.util.concurrent. Je recommande fortement "Java Concurrency in Practice". C'est un excellent livre qui couvre presque tout ce que vous devez savoir.

En ce qui concerne votre implémentation particulière, comme je l’ai noté dans les commentaires, ne démarrez pas Threads from Constructors - cela peut être dangereux.

Laissant cela de côté, la deuxième mise en œuvre semble meilleure. Vous ne voulez pas mettre des files d'attente dans des champs statiques. Vous êtes probablement en train de perdre de la flexibilité pour rien.

Si vous voulez aller de l'avant avec votre propre implémentation (pour des raisons d'apprentissage, je suppose?), Fournissez au moins une méthode start(). Vous devez construire l'objet (vous pouvez instancier l'objet Thread), puis appeler start() pour démarrer le thread.

Edit: ExecutorService possède sa propre file d'attente, ce qui peut prêter à confusion. Voici quelque chose pour vous aider à démarrer.

public class Main {
    public static void main(String[] args) {
        //The numbers are just silly tune parameters. Refer to the API.
        //The important thing is, we are passing a bounded queue.
        ExecutorService consumer = new ThreadPoolExecutor(1,4,30,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(100));

        //No need to bound the queue for this executor.
        //Use utility method instead of the complicated Constructor.
        ExecutorService producer = Executors.newSingleThreadExecutor();

        Runnable produce = new Produce(consumer);
        producer.submit(produce);   
    }
}

class Produce implements Runnable {
    private final ExecutorService consumer;

    public Produce(ExecutorService consumer) {
        this.consumer = consumer;
    }

    @Override
    public void run() {
        Pancake cake = Pan.cook();
        Runnable consume = new Consume(cake);
        consumer.submit(consume);
    }
}

class Consume implements Runnable {
    private final Pancake cake;

    public Consume(Pancake cake){
        this.cake = cake;
    }

    @Override
    public void run() {
        cake.eat();
    }
}

EDIT: Pour le producteur, au lieu de while(true), vous pouvez faire quelque chose comme:

@Override
public void run(){
    while(!Thread.currentThread().isInterrupted()){
        //do stuff
    }
}

De cette façon, vous pouvez arrêter l'exécuteur en appelant .shutdownNow(). Si vous utilisiez while(true), il ne s’arrêterait pas.

Notez également que le Producer est toujours vulnérable à RuntimeExceptions (c’est-à-dire qu’un RuntimeException arrêtera le traitement)

17
Enno Shioji

J'ai étendu la réponse proposée par Cletus à un exemple de code de travail.

  1. Un ExecutorService (pes) accepte Producer tâches.
  2. Un ExecutorService (ces) accepte des tâches Consumer.
  3. Producer et Consumer partage BlockingQueue.
  4. Plusieurs tâches Producer génèrent des nombres différents.
  5. N'importe laquelle des tâches Consumer peut utiliser le nombre généré par Producer

Code:

import Java.util.concurrent.*;

public class ProducerConsumerWithES {
    public static void main(String args[]){
         BlockingQueue<Integer> sharedQueue = new LinkedBlockingQueue<Integer>();

         ExecutorService pes = Executors.newFixedThreadPool(2);
         ExecutorService ces = Executors.newFixedThreadPool(2);

         pes.submit(new Producer(sharedQueue,1));
         pes.submit(new Producer(sharedQueue,2));
         ces.submit(new Consumer(sharedQueue,1));
         ces.submit(new Consumer(sharedQueue,2));
         // shutdown should happen somewhere along with awaitTermination
         / * https://stackoverflow.com/questions/36644043/how-to-properly-shutdown-Java-executorservice/36644320#36644320 */
         pes.shutdown();
         ces.shutdown();
    }
}
class Producer implements Runnable {
    private final BlockingQueue<Integer> sharedQueue;
    private int threadNo;
    public Producer(BlockingQueue<Integer> sharedQueue,int threadNo) {
        this.threadNo = threadNo;
        this.sharedQueue = sharedQueue;
    }
    @Override
    public void run() {
        for(int i=1; i<= 5; i++){
            try {
                int number = i+(10*threadNo);
                System.out.println("Produced:" + number + ":by thread:"+ threadNo);
                sharedQueue.put(number);
            } catch (Exception err) {
                err.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable{
    private final BlockingQueue<Integer> sharedQueue;
    private int threadNo;
    public Consumer (BlockingQueue<Integer> sharedQueue,int threadNo) {
        this.sharedQueue = sharedQueue;
        this.threadNo = threadNo;
    }
    @Override
    public void run() {
        while(true){
            try {
                int num = sharedQueue.take();
                System.out.println("Consumed: "+ num + ":by thread:"+threadNo);
            } catch (Exception err) {
               err.printStackTrace();
            }
        }
    }   
}

sortie:

Produced:11:by thread:1
Produced:21:by thread:2
Produced:22:by thread:2
Consumed: 11:by thread:1
Produced:12:by thread:1
Consumed: 22:by thread:1
Consumed: 21:by thread:2
Produced:23:by thread:2
Consumed: 12:by thread:1
Produced:13:by thread:1
Consumed: 23:by thread:2
Produced:24:by thread:2
Consumed: 13:by thread:1
Produced:14:by thread:1
Consumed: 24:by thread:2
Produced:25:by thread:2
Consumed: 14:by thread:1
Produced:15:by thread:1
Consumed: 25:by thread:2
Consumed: 15:by thread:1

Remarque. Si vous n'avez pas besoin de plusieurs producteurs et consommateurs, conservez un seul producteur et consommateur. J'ai ajouté plusieurs producteurs et consommateurs afin de présenter les fonctionnalités de Blocking Queue parmi plusieurs producteurs et consommateurs.

8
Ravindra babu

Vous réinventez la roue.

Si vous avez besoin de persistance et d’autres fonctionnalités d’entreprise, utilisez JMS (je suggérerais ActiveMq ).

Si vous avez besoin de files d’attente rapides en mémoire, utilisez l’une des implémentations Java Queue .

Si vous devez prendre en charge Java 1.4 ou une version antérieure, utilisez le package excellent simultané de Doug Lea.

8
flybywire

C'est un code très simple.

import Java.util.*;

// @author : rootTraveller, June 2017

class ProducerConsumer {
    public static void main(String[] args) throws Exception {
        Queue<Integer> queue = new LinkedList<>();
        Integer buffer = new Integer(10);  //Important buffer or queue size, change as per need.

        Producer producerThread = new Producer(queue, buffer, "PRODUCER");
        Consumer consumerThread = new Consumer(queue, buffer, "CONSUMER");

        producerThread.start();  
        consumerThread.start();
    }   
}

class Producer extends Thread {
    private Queue<Integer> queue;
    private int queueSize ;

    public Producer (Queue<Integer> queueIn, int queueSizeIn, String ThreadName){
        super(ThreadName);
        this.queue = queueIn;
        this.queueSize = queueSizeIn;
    }

    public void run() {
        while(true){
            synchronized (queue) {
                while(queue.size() == queueSize){
                    System.out.println(Thread.currentThread().getName() + " FULL         : waiting...\n");
                    try{
                        queue.wait();   //Important
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }

                //queue empty then produce one, add and notify  
                int randomInt = new Random().nextInt(); 
                System.out.println(Thread.currentThread().getName() + " producing... : " + randomInt); 
                queue.add(randomInt); 
                queue.notifyAll();  //Important
            } //synchronized ends here : NOTE
        }
    }
}

class Consumer extends Thread {
    private Queue<Integer> queue;
    private int queueSize;

    public Consumer(Queue<Integer> queueIn, int queueSizeIn, String ThreadName){
        super (ThreadName);
        this.queue = queueIn;
        this.queueSize = queueSizeIn;
    }

    public void run() {
        while(true){
            synchronized (queue) {
                while(queue.isEmpty()){
                    System.out.println(Thread.currentThread().getName() + " Empty        : waiting...\n");
                    try {
                        queue.wait();  //Important
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }

                //queue not empty then consume one and notify
                System.out.println(Thread.currentThread().getName() + " consuming... : " + queue.remove());
                queue.notifyAll();
            } //synchronized ends here : NOTE
        }
    }
}
2
roottraveller
  1. Code Java "BlockingQueue" qui a synchronisé les méthodes put et get.
  2. Code Java "Producer", thread producteur pour produire des données.
  3. Code Java "Consommateur", thread consommateur à consommer les données produites.
  4. Code Java "ProducerConsumer_Main", fonction principale pour démarrer le thread producteur et consommateur.

BlockingQueue.Java

public class BlockingQueue 
{
    int item;
    boolean available = false;

    public synchronized void put(int value) 
    {
        while (available == true)
        {
            try 
            {
                wait();
            } catch (InterruptedException e) { 
            } 
        }

        item = value;
        available = true;
        notifyAll();
    }

    public synchronized int get()
    {
        while(available == false)
        {
            try
            {
                wait();
            }
            catch(InterruptedException e){
            }
        }

        available = false;
        notifyAll();
        return item;
    }
}

Consumer.Java

package com.sukanya.producer_Consumer;

public class Consumer extends Thread
{
    blockingQueue queue;
    private int number;
    Consumer(BlockingQueue queue,int number)
    {
        this.queue = queue;
        this.number = number;
    }

    public void run()
    {
        int value = 0;

        for (int i = 0; i < 10; i++) 
        {
            value = queue.get();
            System.out.println("Consumer #" + this.number+ " got: " + value);
        }
    }
}

ProducerConsumer_Main.Java

package com.sukanya.producer_Consumer;

public class ProducerConsumer_Main 
{
    public static void main(String args[])
    {
        BlockingQueue queue = new BlockingQueue();
        Producer producer1 = new Producer(queue,1);
        Consumer consumer1 = new Consumer(queue,1);
        producer1.start();
        consumer1.start();
    }
}
1
Kasthuri