生產(chǎn)者消費者模式介紹
生產(chǎn)者消費者模式是通過一個容器來解決生產(chǎn)者和消費者的強耦合問題。生產(chǎn)者和消費者彼此之間不直接通訊,而通過阻塞隊列來進行通訊,所以生產(chǎn)者生產(chǎn)完數(shù)據(jù)之后不用等待消費者處理,直接扔給阻塞隊列,消費者不找生產(chǎn)者要數(shù)據(jù),而是直接從阻塞隊列里取,阻塞隊列就相當于一個緩沖區(qū),平衡了生產(chǎn)者和消費者的處理能力。
這個阻塞隊列就是用來給生產(chǎn)者和消費者解耦的。阻塞隊列如何實現(xiàn)高并發(fā)多線程安全也是生產(chǎn)者消費者模式中的核心關鍵。
在日常開發(fā)過程中,我們常常會遇到一些高并發(fā)場景,例如很多秒殺場景,其實真實的秒殺場景會很復雜,這里只是簡單描述下秒殺場景下的生產(chǎn)者消費者模式,在秒殺場景下生產(chǎn)者是普通參與秒殺的用戶,消費者是秒殺系統(tǒng),通常來說這樣的場景下秒殺用戶是非常多的,如果系統(tǒng)采用常規(guī)的實時同步交易,那么勢必造成系統(tǒng)處理線程池被瞬間占滿,后續(xù)請求全部被丟棄,這樣造成的用戶體驗是非常差的,而且系統(tǒng)可能會出現(xiàn)快速雪崩。在多線程開發(fā)當中,如果生產(chǎn)者處理速度很快,而消費者處理速度很慢,那么生產(chǎn)者就必須等待消費者處理完,才能繼續(xù)生產(chǎn)數(shù)據(jù)。同樣的道理,如果消費者的處理能力大于生產(chǎn)者,那么消費者就必須等待生產(chǎn)者。為了解決這個問題于是引入了生產(chǎn)者和消費者模式。秒殺場景是個非常適合生產(chǎn)者、消費者的模式,通過中間的緩沖隊列解決秒殺請求接收和秒殺處理兩者之間的處理時間差,并且能夠在過程中通過反欺詐和公平算法來保證消費者的公平利益。下面我們就來用java實現(xiàn)下生產(chǎn)者和消費者模式,這里實現(xiàn)的是可以直接用于生產(chǎn)環(huán)境的架構,并不是簡單的使用Queue做個簡單的進棧出棧,而是通過java.util.concurrent下的相關類實現(xiàn)生產(chǎn)者消費者模式的多線程方案。
Java實現(xiàn)生產(chǎn)者消費者模式
隊列的特性:先進先出(FIFO)—先進入隊列的元素先出隊列(可以理解為我們生活中的排隊情況,早辦完,早滾蛋)。生產(chǎn)者(Producer)往隊列里發(fā)布(publish)事件,消費者(Consumer)獲得通知,消費事件;如果隊列中沒有事件時,消費者堵塞,直到生產(chǎn)者發(fā)布了新事件。
說到隊列,那就不得不提到Java中的concurrent包,其主要實現(xiàn)包括ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue、LinkedTransferQueue。下面,簡單介紹下:
ArrayBlockingQueue:基于數(shù)組形式的隊列,通過加鎖的方式,來保證多線程情況下數(shù)據(jù)的安全;
LinkedBlockingQueue:基于鏈表形式的隊列,也通過加鎖的方式,來保證多線程情況下數(shù)據(jù)的安全;
ConcurrentLinkedQueue:基于鏈表形式的隊列,通過compare and swap(簡稱CAS)協(xié)議的方式,來保證多線程情況下數(shù)據(jù)的安全,不加鎖,主要使用了Java中的sun.misc.Unsafe類來實現(xiàn);
LinkedTransferQueue:同上;
因為LinkedBlockingQueue采用了樂觀鎖方案,所以性能是非常高的,下面我們就用LinkedBlockingQueue作為隊列緩沖區(qū)來實現(xiàn)生產(chǎn)者消費者模式。
待處理數(shù)據(jù)類
首先我們需要實現(xiàn)一個緩沖隊列中的待處理類,這里例子中實現(xiàn)的比較簡單,只是設置了一個int類型的變量,重寫了構造函數(shù)并定義了get方法,大家可以根據(jù)自己的需要定義相關的內(nèi)容。
public class PCData {
private final int intData;
public PCData(int intData) {
this.intData = intData;
}
public int getIntData() {
return intData;
}
@Override
public String toString() {
return "PCData{" +
"intData=" + intData +
'}';
}
}
生產(chǎn)者類
下面我們定義生產(chǎn)者類,在生產(chǎn)者類中需要定義一個緩沖隊列,這里使用了剛才提到的BlockingDeque。
private BlockingDeque<PCData> queue;
生產(chǎn)者中還需要再定義一個靜態(tài)的AtomicInteger類型的對象,用于多線程中共享數(shù)據(jù),用于生成PCData,為什么使用AtomicInteger類型,是因為AtomicInteger類型已經(jīng)實現(xiàn)了線程安全的自增功能,在實際項目使用過程中,這個值可能是UUID或者其他的全局唯一的數(shù)值。
private static AtomicInteger count = new AtomicInteger();
還需要重寫構造方法,在生成生產(chǎn)者的時候使用同一個緩沖隊列,來保證生產(chǎn)者和開發(fā)者都使用一樣的隊列,在實際項目中也可以定一個全局的隊列,來保證所有的生產(chǎn)者和消費者都使用同一個對列。
//定義入?yún)锽lockingQueue的構造函數(shù)
public Producer(BlockingDeque<PCData> queue){
this.queue = queue;
}
生產(chǎn)者的核心方法中主要實現(xiàn)了創(chuàng)建PCData類并將該待處理對象放入緩沖隊列中,這里為了模擬處理耗時,sleep了1秒鐘,所有繼承子BlockingDeque的隊列類都實現(xiàn)了offer方法,該方法主要是將待處理對象放入緩沖隊列中,這樣生產(chǎn)者就完成了生產(chǎn)者的基本工作,創(chuàng)建待處理類對象,并將其放入隊列。
Thread.sleep(1000);
data = new PCData(count.incrementAndGet());
queue.offer(data);
下面是整個Producer的代碼:
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author: feiweiwei
* @Description: 生產(chǎn)者
* @Created Date: 17:14 17/9/10.
* @Modify by:
*/
public class Producer implements Runnable {
private volatile boolean isrunning = true;
//內(nèi)存緩沖隊列
private BlockingDeque<PCData> queue;
private static AtomicInteger count = new AtomicInteger();
//定義入?yún)锽lockingQueue的構造函數(shù)
public Producer(BlockingDeque<PCData> queue){
this.queue = queue;
}
public void stop(){
this.isrunning = false;
}
@Override
public void run() {
PCData data = null;
System.out.println("producer id = " + Thread.currentThread().getId());
while (isrunning) {
try {
Thread.sleep(1000);
data = new PCData(count.incrementAndGet());
queue.offer(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消費者類
消費者類的核心工作就是將待處理數(shù)據(jù)從緩沖隊列中取出,并處理。
在消費者類中同樣有個BlockingDeque<PCData>的對象,同樣也是在創(chuàng)建消費者類的時候從外部傳入,這樣可以保證所有生產(chǎn)者和消費者使用一樣的隊列。
在核心處理邏輯中通過BlockingDeque的take方法取出待處理對象,然后就可以對該對象進行處理了,調(diào)用take方法后,該待處理對象也自動從queue中彈出。
下面是消費者實現(xiàn)代碼:
import java.util.concurrent.BlockingDeque;
/**
* @Author: feiweiwei
* @Description: 消費者類
* @Created Date: 17:26 17/9/10.
* @Modify by:
*/
public class Customer implements Runnable {
private BlockingDeque<PCData> queue;
private volatile boolean isrunning = true;
//定義入?yún)锽lockingQueue的構造函數(shù)
public Customer(BlockingDeque<PCData> queue){
this.queue = queue;
}
public void stop(){
this.isrunning = false;
}
@Override
public void run() {
System.out.println("customer id = " + Thread.currentThread().getId());
while (isrunning){
try {
PCData data = queue.take();
if ( null != data){
int re = data.getIntData() * data.getIntData();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getId() + " data is " + re + "done!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主調(diào)用Main
在主調(diào)用Main中,我們先創(chuàng)建一個隊列長度為10的LinkedBlockingDeque對象作為緩沖隊列。
BlockingDeque<PCData> queue = new LinkedBlockingDeque<PCData>(10);
再分別創(chuàng)建10個生產(chǎn)者對象和2個消費者,并將剛才創(chuàng)建的queue對象作為構造函數(shù)入?yún)ⅰ?/p>
Producer[] producers = new Producer[10];
Customer[] customers = new Customer[2];
for(int i=0; i<10; i++){
producers[i] = new Producer(queue);
}
for(int j=0; j<2; j++){
customers[j] = new Customer(queue);
}
創(chuàng)建一個線程池將生產(chǎn)者和消費者調(diào)用起來,這里的線程池大家可以使用自定義的線程池。
ExecutorService es = Executors.newCachedThreadPool();
for(Producer producer : producers){
es.execute(producer);
}
for(Customer customer : customers){
es.execute(customer);
}
下面是Main代碼:
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 17:29 17/9/10.
* @Modify by:
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
BlockingDeque<PCData> queue = new LinkedBlockingDeque<PCData>(10);
Producer[] producers = new Producer[10];
Customer[] customers = new Customer[2];
for(int i=0; i<10; i++){
producers[i] = new Producer(queue);
}
for(int j=0; j<2; j++){
customers[j] = new Customer(queue);
}
ExecutorService es = Executors.newCachedThreadPool();
for(Producer producer : producers){
es.execute(producer);
}
for(Customer customer : customers){
es.execute(customer);
}
Thread.sleep(10000);
for(Producer producer : producers){
producer.stop();
}
for(Customer customer : customers){
customer.stop();
}
es.shutdown();
}
}
Disruptor實現(xiàn)生產(chǎn)者消費者模式
剛才那個是我們自己使用java.util.concurrent下的類實現(xiàn)的生產(chǎn)者消費者模式,目前業(yè)界已經(jīng)有比較成熟的方案,這里向大家推薦LMAX公司開源的Disruptor框架,Disruptor是一個開源的框架,可以在無鎖的情況下對隊列進行操作,那么這個隊列的設計就是Disruptor的核心所在。
在Disruptor中,采用了RingBuffer來作為隊列的數(shù)據(jù)結構,RingBuffer就是一個環(huán)形的數(shù)組,既然是數(shù)組,我們便可對其設置大小。在這個ringBuffer中,除了數(shù)組之外,還有一個序列號,是用來指向數(shù)組中的下一個可用元素,供生產(chǎn)者使用或者消費者使用,也就是生產(chǎn)者可以生產(chǎn)的地方,或者消費者可以消費的地方。在Disruptor中使用的是位運算,并且在Disruptor中數(shù)組內(nèi)的元素并不會被刪除,而是新數(shù)據(jù)來覆蓋原有數(shù)據(jù),所以整個環(huán)鏈的處理效率非常高。
下面我們使用Disruptor來實現(xiàn)剛才用jdk自帶庫實現(xiàn)的生產(chǎn)者消費者。
Disruptor主要類
Disruptor:Disruptor的入口,主要封裝了環(huán)形隊列RingBuffer、消費者集合ConsumerRepository的引用;主要提供了獲取環(huán)形隊列、添加消費者、生產(chǎn)者向RingBuffer中添加事件(可以理解為生產(chǎn)者生產(chǎn)數(shù)據(jù))的操作;
RingBuffer:Disruptor中隊列具體的實現(xiàn),底層封裝了Object[]數(shù)組;在初始化時,會使用Event事件對數(shù)組進行填充,填充的大小就是bufferSize設置的值;此外,該對象內(nèi)部還維護了Sequencer(序列生產(chǎn)器)具體的實現(xiàn);
Sequencer:序列生產(chǎn)器,分別有MultiProducerSequencer(多生產(chǎn)者序列生產(chǎn)器) 和 SingleProducerSequencer(單生產(chǎn)者序列生產(chǎn)器)兩個實現(xiàn)類。上面的例子中,使用的是SingleProducerSequencer;在Sequencer中,維護了消費者的Sequence(序列對象)和生產(chǎn)者自己的Sequence(序列對象);以及維護了生產(chǎn)者與消費者序列沖突時候的等待策略WaitStrategy;
Sequence:序列對象,內(nèi)部維護了一個long型的value,這個序列指向了RingBuffer中Object[]數(shù)組具體的角標。生產(chǎn)者和消費者各自維護自己的Sequence;但都是指向RingBuffer的Object[]數(shù)組;
Wait Strategy:等待策略。當沒有可消費的事件時,消費者根據(jù)特定的策略進行等待;當沒有可生產(chǎn)的地方時,生產(chǎn)者根據(jù)特定的策略進行等待;
Event:事件對象,就是我們Ringbuffer中存在的數(shù)據(jù),在Disruptor中用Event來定義數(shù)據(jù),并不存在Event類,它只是一個定義;
EventProcessor:事件處理器,單獨在一個線程內(nèi)執(zhí)行,判斷消費者的序列和生產(chǎn)者序列關系,決定是否調(diào)用我們自定義的事件處理器,也就是是否可以進行消費;
EventHandler:事件處理器,由用戶自定義實現(xiàn),也就是最終的事件消費者,需要實現(xiàn)EventHandler接口;
Producer:事件生產(chǎn)者,也就是我們上面代碼中最后那部門的for循環(huán);
待處理類
Disruptor的待處理類和自己實現(xiàn)的待處理類沒有本質(zhì)的區(qū)別,可以按照自己要求進行定義。
public class PCData {
private int data;
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
待處理類工廠
這里需要實現(xiàn)disruptor的EventFactory接口,并且實現(xiàn)newInstance方法。這里我們實現(xiàn)的newInstance方法,其實就是創(chuàng)建待處理類的對象,該工廠類在創(chuàng)建Disruptor對象的時候會使用到。
import com.lmax.disruptor.EventFactory;
/**
* @Author: feiweiwei
* @Description: 待處理類工廠
* @Created Date: 18:55 17/9/10.
* @Modify by:
*/
public class PCDataFactory implements EventFactory<PCData> {
@Override
public PCData newInstance() {
return new PCData();
}
}
disruptor生產(chǎn)者類
同樣需要在生產(chǎn)者中定義一個RingBuffer<PCData>的環(huán)形隊列,還需要實現(xiàn)一個push的方法,通過ringBuffer.next()取到下一個待處理類序列號,使用ringBuffer.get(sequence)獲取到這個序列號對應的待處理類,并對待處理類進行賦值為新的待處理類。
最后通過ringBuffer.publish(sequence)才會將待處理對象發(fā)布出來,消費者才能看到。
import com.lmax.disruptor.RingBuffer;
/**
* @Author: feiweiwei
* @Description: disruptor生產(chǎn)者類
* @Created Date: 18:56 17/9/10.
* @Modify by:
*/
public class Producer {
private final RingBuffer<PCData> ringBuffer;
public Producer(RingBuffer<PCData> ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void pushData(int data){
long sequence = ringBuffer.next();
try{
PCData event = ringBuffer.get(sequence);
event.setData(data);
}finally {
ringBuffer.publish(sequence);
}
}
}
disruptor消費者
disruptor的消費者類需要實現(xiàn)WorkHandler接口,并實現(xiàn)onEvent方法來處理待處理類,例子中只是對待處理類中的值做了平方。
import com.lmax.disruptor.WorkHandler;
/**
* @Author: feiweiwei
* @Description: disruptor消費者
* @Created Date: 18:52 17/9/10.
* @Modify by:
*/
public class Consumer implements WorkHandler<PCData> {
@Override
public void onEvent(PCData pcData) throws Exception {
System.out.println(Thread.currentThread().getId() +
"Event = " + pcData.getData()*pcData.getData());
}
}
Main
待處理類、待處理工廠、生產(chǎn)者、消費者都定義好之后就可以進行使用了,定義一個緩行隊列為1024的disruptor對象,這里構造函數(shù)入?yún)⒖疵志椭懒耍芎唵巍?/p>
PCDataFactory factory = new PCDataFactory();
int bufferSize = 1024;
Disruptor<PCData> disruptor = new Disruptor<PCData>(factory,bufferSize,executor,
ProducerType.MULTI,new BlockingWaitStrategy());
給disruptor對象定義消費者,這里就簡單定義兩個consumer作為生產(chǎn)者。
disruptor.handleEventsWithWorkerPool(new Consumer(),new Consumer());
初始化Producer并且將ringBuffer作為構造函數(shù)入?yún)ⅲ⑼ㄟ^生產(chǎn)者循環(huán)100次將數(shù)據(jù)push入隊列,消費者會自動從隊列取值進行處理。
RingBuffer<PCData> ringBuffer = disruptor.getRingBuffer();
Producer producer = new Producer(ringBuffer);
for(int i=0; i<100; i++){
producer.pushData(i);
Thread.sleep(100);
System.out.println("push data " + i);
}
以下為Main全部代碼:
package com.monkey01.producercustomer.disruptor;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @Author: feiweiwei
* @Description:
* @Created Date: 18:59 17/9/10.
* @Modify by:
*/
public class Main {
public static void main(String args[]) throws InterruptedException {
Executor executor = Executors.newCachedThreadPool();
PCDataFactory factory = new PCDataFactory();
int bufferSize = 1024;
Disruptor<PCData> disruptor = new Disruptor<PCData>(factory,bufferSize,executor,
ProducerType.MULTI,new BlockingWaitStrategy());
disruptor.handleEventsWithWorkerPool(new Consumer(),
new Consumer());
disruptor.start();
RingBuffer<PCData> ringBuffer = disruptor.getRingBuffer();
Producer producer = new Producer(ringBuffer);
for(int i=0; i<100; i++){
producer.pushData(i);
Thread.sleep(100);
System.out.println("push data " + i);
}
disruptor.shutdown();
}
}
總結
大家看到這里也基本對生產(chǎn)者、消費者模式有個比較深入的了解了,也可以按照文中的例子,在自己的項目中使用,這個模式在日常項目中還是比較常見的,希望大家能夠熟練使用該模式。