Fios de produtor/consumidor usando uma fila
-
22-09-2019 - |
Pergunta
Eu gostaria de criar algum tipo de Producer/Consumer
aplicativo de encadeamento. Mas não tenho certeza de qual a melhor maneira de implementar uma fila entre os dois.
Então, eu tenho algumas idéias (ambas podem estar totalmente erradas). Eu gostaria de saber qual seria melhor e se ambos chuparem, qual seria a melhor maneira de implementar a fila. É principalmente minha implementação da fila nesses exemplos com os quais estou preocupado. Estou estendendo uma aula de filas que é uma aula interna e é segura para tópicos. Abaixo estão dois exemplos com 4 classes cada.
Classe principal-
public class SomeApp
{
private Consumer consumer;
private Producer producer;
public static void main (String args[])
{
consumer = new Consumer();
producer = new Producer();
}
}
Classe de consumo
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 produtora
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 fila
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 principal-
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 consumo
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 produtora
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 fila
//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();
}
}
E ir!
Solução
O Java 5+ tem todas as ferramentas necessárias para esse tipo de coisa. Você vai querer:
- Coloque todos os seus produtores em um
ExecutorService
; - Coloque todos os seus consumidores em outro
ExecutorService
; - Se necessário, comunique -se entre os dois usando um
BlockingQueue
.
Eu digo "se necessário" para (3) porque, pela minha experiência, é uma etapa desnecessária. Tudo o que você faz é enviar novas tarefas ao Serviço de Executor do Consumidor. Então:
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);
Então o producers
enviar diretamente para consumers
.
Outras dicas
Ok, como outros observam, a melhor coisa a fazer é usar java.util.concurrent
pacote. Eu recomendo "java concorrência na prática". É um ótimo livro que cobre quase tudo o que você precisa saber.
Quanto à sua implementação específica, como observei nos comentários, não inicie threads dos construtores - pode ser inseguro.
Deixando isso de lado, a segunda implementação parece melhor. Você não quer colocar filas em campos estáticos. Você provavelmente está apenas perdendo a flexibilidade por nada.
Se você quiser seguir em frente com sua própria implementação (para fins de aprendizado, eu acho?), Forneça um start()
Método pelo menos. Você deve construir o objeto (você pode instanciar o Thread
objeto) e depois ligar start()
Para iniciar o tópico.
Editar: ExecutorService
Tenha sua própria fila para que isso possa ser confuso. Aqui está algo para você começar.
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();
}
}
Editar mais: para produtor, em vez de while(true)
, você pode fazer algo como:
@Override
public void run(){
while(!Thread.currentThread().isInterrupted()){
//do stuff
}
}
Dessa forma, você pode desligar o executor ligando .shutdownNow()
. Se você usar while(true)
, não vai desligar.
Observe também que o Producer
ainda é vulnerável a RuntimeExceptions
(ou seja RuntimeException
interromperá o processamento)
Você está reinventando a roda.
Se você precisar de persistência e outros recursos corporativos, use Jms (Eu sugiro ActiveMq).
Se você precisar de filas rápidas na memória, use uma das imementações do Java's Fila.
Se você precisar apoiar o Java 1.4 ou anterior, use o excelente de Doug Lea simultâneo pacote.
Eu estendi Cletus proposto Resposta ao Exemplo de Código de Trabalho.
- Um
ExecutorService
(PES) aceitaProducer
tarefas. - Um
ExecutorService
(CES) aceitaConsumer
tarefas. - Ambos
Producer
eConsumer
açõesBlockingQueue
. - Múltiplo
Producer
tarefas gera números diferentes. - Qualquer um de
Consumer
tarefas podem consumir número gerado porProducer
Código:
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();
}
}
}
}
resultado:
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
Observação. Se você não precisar de vários produtores e consumidores, mantenha um único produtor e consumidor. Eu adicionei vários produtores e consumidores para mostrar recursos de BlockingQueue entre vários produtores e consumidores.
Este é um código muito simples.
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
}
}
}
- Código Java "BlockingQueue", que sincronizou o método Put and Get.
- Código Java "Produtor", thread do produtor para produzir dados.
- Código Java "Consumidor", encadeamento do consumidor para consumir os dados produzidos.
- Código Java "ProdidConsumer_Main", função principal para iniciar o produtor e o thread do consumidor.
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);
}
}
}
ProduterConsumer_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();
}
}