多線程生產者、消費者模式中,如何停止消費者 ?多生產者情況下對“毒丸”策略的應用
生產者、消費者模式是多線程中的經典問題。通過中間的緩衝隊列,使得生產者和消費者的速度可以相互調節。
發散:一個主線程控制多個子線程,多個主線程控制多個子線程(countDownLunch使用理解)
對於比較常見的單生產者、多消費者的情況,主要有以下兩種策略:
-
通過volatile boolean producerDone =false 來標示是否完成。生產者結束後標示爲true, 消費者輪詢這個變量來決定自己是否退出。 這種方式對producerDone產生比較大的爭用,實現起來也有諸多問題需要考慮。
-
比較經典的“毒丸”策略,生產者結束後,把一個特別的對象:“毒丸”對象放入隊列。消費者從隊列中拿到對象後,判斷是否是毒丸對象。如果是普通非毒丸對象,則正常消費。如果是毒丸對象,則放回隊列(殺死其他消費者),然後結束自己。這種方式不會對結束狀態產生爭用,是比較好的方式。
由於“毒丸”策略是在單生產者多消費者情況下的。對於多生產者的情況,需要對之進行一些修改。我的想法是這樣的。用Countdownlatch作爲生產者計數器。所有生產者結束後,由協調者放入毒丸對象,消費者退出過程是一樣的。上代碼:
三個類 :
Coordinator: 啓動生產者消費者,提供隊列、計數器。生產者全部結束後,放入毒丸。
Producer: 隨機生產和結束,結束前使countdownlatch + 1
Consumer: 判斷毒丸對象。如果是毒丸,放回隊列(殺死其他消費者),然後自己退出。
package com.XXX.XXX.XXX.poisonpill;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
/**
* @author mossGao
* @Description 協調器 啓動生產者消費者,提供隊列、計數器。生產者全部結束後,放入毒丸。
* @create 2020-03-27 16:37
**/
public class Coordinator {
public static final Object POISON_PILL = new Object();//special object to kill consumers
private int productCount = 3;
private int consumerCount = 5;
public void startAll() throws Exception {
BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(20);
CountDownLatch noMoreToProduce = new CountDownLatch(productCount);
//start consumers;
for (int i = 0; i < consumerCount; i++) {
new Thread(new Consumer("consumer " + i, queue)).start();
}
//start producers;
for (int i = 0; i < productCount; i++) {
new Thread(new Producer("producer " + i, queue, noMoreToProduce)).start();
}
//wait until all producer down
noMoreToProduce.await();
System.out
.println("All producer finished, putting POISON_PILL to the queue to stop consumers!");
//put poison pill
queue.put(POISON_PILL);
}
public static void main(String[] args) throws Exception {
new Coordinator().startAll();
}
}
package com.XXX.XXX.XXX.poisonpill;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
/**
* @author mossGao
* @Description 隨機生產和結束,結束前使countdownlatch + 1
* @create 2020-03-27 16:38
**/
public class Producer implements Runnable {
private String name;
private CountDownLatch noMoreToProduce;
private BlockingQueue<Object> queue;
private Random random = new Random();
public Producer(String name, BlockingQueue<Object> queue, CountDownLatch noMoreToProduce) {
this.name = name;
this.queue = queue;
this.noMoreToProduce = noMoreToProduce;
}
@Override
public void run() {
System.out.println(name + " started.");
try {
while (true) {
Object item = randomProduce();
if (item == null) {
break; //break if no more item
}
queue.put(item);
System.out.println(name + " produced one.");
}
} catch (InterruptedException e) {
//log
} finally {
System.out.println(name + " finished.");
noMoreToProduce.countDown();//count down to signal "I finished."
}
}
private Object randomProduce() {
if (random.nextBoolean()) {
return new Object();
}
return null;
}
}
package com.XXX.XXX.XXX.poisonpill;
import java.util.concurrent.BlockingQueue;
/**
* @author mossGao
* @Description 判斷毒丸對象。如果是毒丸,放回隊列(殺死其他消費者),然後自己退出。
* @create 2020-03-27 16:39
**/
public class Consumer implements Runnable {
private String name;
private BlockingQueue<Object> queue;
public Consumer(String name, BlockingQueue<Object> queue) {
this.name = name;
this.queue = queue;
}
@Override
public void run() {
try {
System.out.println(name + " started.");
while (true) {
Object item = queue.take();
//poison pill processing
if (item == Coordinator.POISON_PILL) {
queue.put(item);//put back to kill others
System.out.println(name + " finished");
break;
}
item = null;//pretend to consume the item;
System.out.println(name + " consumed one");
}
} catch (InterruptedException e) {
}
}
}