线程基础(三十三)

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。

接上篇,本篇讲解线程另外一个设计模式:Producer-Consumer Pattern.

概念

Producer是生产,生产者的意思, 指生产数据的线程, Consumer 则是消费,消费者的意思. 指使用数据的线程.

Producer-Consumer 模式主要目标:生产者生产数据能安全得交给消费者进行处理.

当Producer 跟 Consumer 都为一个线程时, Producer-Consumer 模式就变为Pipe模式

参与角色

Producer-Consumer模式参与角色:
Data: 资源对象(数据对象)
Data角色由Producer角色生成,供Consumer角色使用,一般都需要放置到一个容器(Channel角色)中进行安全管理.

Producer: 生产者
Producer角色生成Data角色,并将其传递给Channel角色存放, Consumer角色再从Channel角色中获取Data角色

Consumer:消费者
Consumer角色从Channel角色中获取Producer角色生成Data角色, 并使用

Channel:通道
Channel角色保管Producer角色生成的Data角色,还能响应Consumer角色处理Data角色的请求,传递Data角色,一般而言,为了确保Data的安全性, Channel角色对Producer角色跟Consumer角色的访问执行互斥处理.

当Channel角色满足接收Data角色条件时, 才接纳Producer角色生成的Data角色, 否则Producer角色一直等待,直到条件满足.
当Channel角色满足传递Data角色条件时,才将Data角色传递给Consumer角色,否则Consumer角色一直等待,直到条件满足.


image.png

模式特征:

1:存在数据创建对象(producter)
2:存在数据使用对象(consumer)
3:存在数据存储对象(channel)
4:存在被操作的对象(data)
5:存在操作条件控制(channel接受与传递data条件)

演示案例

需求:5个生产者生产Data数据, 5个消费者消费Data数据, data至多只能存放10个, 超过10个, 生产者等待, 少于1个消费者等待.

//数据
public class Data {
    public void show(){
        System.out.println("data对象 dosomething....");
    }
}
//通道
public class Channel {
    private int count;  //channel存储data个数
    @Getter
    private List<Data> list = new ArrayList<Data>();

    public Channel(int count){
        this.count = count;
    }
    public synchronized void setData(Data data) throws InterruptedException {
        if(list.size() < count){
            list.add(data);
            System.out.println(Thread.currentThread().getName() + " 生产了一个数据, 目前数据个数:" +list.size());
            notifyAll();  //唤醒所有等待的消费者
        }else{
            System.out.println(Thread.currentThread().getName() + " 检查目前数据个数:" +list.size() +" 不需要生产,等待....");
            wait(); //超过容量,等待
        }
    }

    public synchronized Data getData() throws InterruptedException {
        Data data = null;
        while(true){ //某个消费者被唤醒后,可以直接进行消费,而不是空返回
            if (list.size() == 0){
                System.out.println(Thread.currentThread().getName() + " 检查目前数据个数:" +list.size() +" 不能消费,等待....");
                wait(); //等待生产者生产数据
            }else{
                data = list.remove(0);//从最底拿起;
                System.out.println(Thread.currentThread().getName() + " 消费了一个数据, 目前数据个数:"+list.size());
                notifyAll(); //唤醒所有等待的消费者
                break;
            }
        }
        return data;
    }

}
//生产者
public class Producter implements Runnable {

    private Channel channel;
    public Producter(Channel channel){
        this.channel = channel;
    }

    public void run() {
        while (true){
            try {
                channel.setData(new Data());
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
public class Consumer implements Runnable {

    private Channel channel;
    public Consumer(Channel channel){
        this.channel = channel;
    }


    public void run() {
       while (true){
           try {
               channel.getData().show();
               Thread.sleep(100);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
    }
}
public class App {

    public static void main(String[] args) {
        Channel channel = new Channel(10);
        for (int i = 0; i < 5; i++) {
            new Thread(new Producter(channel), "producter_" + i).start();
        }
        for (int i = 0; i < 5; i++) {
            new Thread(new Consumer(channel), "consumer_" + i).start();
        }

    }
}
producter_0 生产了一个数据, 目前数据个数:1
producter_3 生产了一个数据, 目前数据个数:2
producter_2 生产了一个数据, 目前数据个数:3
producter_4 生产了一个数据, 目前数据个数:4
producter_1 生产了一个数据, 目前数据个数:5
consumer_0 消费了一个数据, 目前数据个数:4
data对象 dosomething....
consumer_1 消费了一个数据, 目前数据个数:3
data对象 dosomething....
consumer_2 消费了一个数据, 目前数据个数:2
data对象 dosomething....
consumer_3 消费了一个数据, 目前数据个数:1
data对象 dosomething....
consumer_4 消费了一个数据, 目前数据个数:0
data对象 dosomething....
producter_2 生产了一个数据, 目前数据个数:1
producter_0 生产了一个数据, 目前数据个数:2
producter_3 生产了一个数据, 目前数据个数:3
producter_4 生产了一个数据, 目前数据个数:4
consumer_3 消费了一个数据, 目前数据个数:3
data对象 dosomething....
consumer_2 消费了一个数据, 目前数据个数:2
data对象 dosomething....
consumer_1 消费了一个数据, 目前数据个数:1
data对象 dosomething....
consumer_0 消费了一个数据, 目前数据个数:0
data对象 dosomething....

几个注意点

1:为什么要存在Channel角色
是否可以将Producter生产的Data数据直接传递给Consumer消费, 跳过Channel这个角色?
想法是好的, 操作性不强, 少了channel中间传递角色, producter生产数据后, 势必需要调用consumer的方法, 执行消费动作, 那么producter生产跟消费便是同一个线程, 这个线程, 先执行生产,然后再执行消费逻辑. 那么这就失去异步操作优势了.

2:Channel 角色的安全性
chanel角色类中的getData与setData方法都使用synchronized 修饰,保证list 数据的操作安全.

3:Channel角色中获取数据的顺序问题
案例中, 使用的ArrayList集合存放Data数据,getData时,从最底位置list.remove(0)获取, 实际运用中可以结合需求,采用不同存储结构:
队列:先进先出
栈:后进先出
优先队列: 优先级高的先出.

4:如果 Producter 跟 Consumer 只有1个时,会怎样?
当Producter跟Consumer都为一个时, 便是管道模式(Pipe模式)

producter_0 生产了一个数据, 目前数据个数:1
consumer_0 消费了一个数据, 目前数据个数:0
data对象 dosomething....
consumer_0 检查目前数据个数:0 不能消费,等待....
producter_0 生产了一个数据, 目前数据个数:1
consumer_0 消费了一个数据, 目前数据个数:0
data对象 dosomething....

一生产,马上使用.经典运用案例:
SynchronousQueue类的实现逻辑

适用场景

Producer-Consumer Pattern 是最实用的异步操作模式, 其操作思想广泛运用于各类MQ中间件中. 而jdk中很多相关集合类中也使用生产者-消费者模式, 比如:
实现BlockingQueue接口的阻塞队列:
ArrayBlockingQueue: 基于数字的阻塞队列
LinkedBlockingQueue: 基于链表的阻塞队列
ProrityBlockingQueue:基于优先级的阻塞队列
DelayQueue: 具有延时特效的阻塞队列
SynchronousQueue:直接传递的阻塞队列
ConcurrentLinkedQueue:没有个数限制的阻塞队列

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容