一、背景 && 定义

多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步。

另一个问题是, 多个线程之间如何协作呢

我们看一个仓库出货问题(更具体一些,快餐店直接放好炸货的架子,不过每次只放一份)

  1. 假设仓库中只能存放一件商品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走进行消费;
  2. 如果仓库中没有商品,那么生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止;
  3. 如果仓库中放有产品,消费者可快速取走并消费,否则停止消费并等待,直到仓库中再次放入产品为止。

这其实就是一个线程同步问题。 生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互为条件。

如果一个快餐店:

先点单,餐出来之后再收钱。这种模式叫BIO-阻塞IO模式。

如果一个快餐店:

先收钱,收完钱消费者在旁边等。这种就是生产者-消费者模式。

这类问题里,同步的候只有 synchronized 是不够的,因为他虽然能解决资源的共享问题,实现资源的同步更新,但是无法 在不同线程之间进行消息传递 (通信)。

所以只有我们之前所说的 加锁排队 是不够的,还要有 通知

定义:

生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。

为了解决双方能力不等而等待的问题,引入对应的解决方案。生产者消费者模型是一种并发协作模型。

二、解决方式介绍

2.1 管程法

  1. 生产者 :负责生产数据的模块(模块可能是方法、对象、线程、进程);
  2. 消费者 :负责处理数据的模块(模块可能是方法、对象、线程、进程);
  3. 缓冲区 :消费者不能直接使用生产者的数据,它们之间有个“缓冲区”(缓冲区一般是队列)。

生产者和消费者都是通过缓冲区进行数据的 放 和 那 。

这样的话,一来可以避免旱的旱死,涝的涝死的问题:不管哪一方过快或者过慢,缓冲区始终有一部分数据;二来能够达到生产者和消费者的解耦,不再直接通信,从而提高效率。

因为容器相当于一个输送商品的管道,所以成为 管程法

2.2 信号灯法

采用类似红灯绿灯的模式,决定车走还是人走。

  • 管程法使用容器的状态来控制,数据在容器中;
  • 而信号灯法只是用信号来给生产者和消费者提醒,他们的交互数据并不由信号灯来保管。

2.3 Object类

jdk 里面 Object 类老早就有提供解决线程间通信的问题的方法:

  1. wait() :表示线程一直等待,直到其他线程通知(也就是调用了notify或者notifyAll方法),与sleep不同,会释放锁;
  2. wait(long timeout) :指定时间;
  3. notify() :唤醒一个处于等待状态的线程;
  4. notifyAll() :唤醒同一个对象上所有调用 wait() 方法的线程,优先级别高的线程优先调度。

这几个方法都是在 同步方法或者同步代码块 中使用,否则会抛出异常。

(很多面试题问 Java 的 Object 类有哪些方法,都是希望得到关于这块的答案,引导多线程)

三、管程法实现

管程法实现的四个角色:

  1. 生产者和消费者都是多线程;
  2. 中间的缓冲区应该是一个容器,并且需要的是一个 并发容器 ,java.util.concurrent包里面已经提供了;
  3. 资源,也就是各个角色来回交换的商品。

利用 Object 类的几个方法,来实现管程法,以下是代码示例:

/*** 协作模型:生产者消费者模型实现:管程法*/public class Cooperation1 {    public static void main(String[] args) {        Container container = new Container();        new Producer(container).start();        new Consumer(container).start();    }}/*** 生产者*/class Producer extends Thread{    Container container;    public Producer(Container container){        this.container = container;    }    @Override    public void run() {        //生产过程        for (int i=0; i<10; i++){            System.out.println("生产第 " + i + " 个馒头");            container.push(new Hamburger(i));        }    }}/*** 消费者*/class Consumer extends Thread{    Container container;    public Consumer(Container container){        this.container = container;    }    @Override    public void run() {        //消费过程        for (int i=0; i<10; i++){            System.out.println("消费第 " + container.pop().id + " 个馒头");        }    }}/*** 缓冲区,操作商品,并和生产者、消费者交互*/class Container{    Hamburger[] food = new Hamburger[10];    private int count = 0;    //存储:生产    public synchronized void push(Hamburger hamburger){        if (count == food.length){            try {                this.wait();//阻塞,但是等待消费者通知后会解除            } catch (InterruptedException e) {                e.printStackTrace();            }        }        food[count++] = hamburger;        this.notifyAll();//说明存在数据了,通知消费者消费    }    //获取:消费    public synchronized Hamburger pop(){        if (count ==0 ){            try {                this.wait();//阻塞,直到生产者通知后会解除            } catch (InterruptedException e) {                e.printStackTrace();            }        }        Hamburger ans = food[--count];        this.notifyAll();//存在空余空间了,通知生产者生产        return ans;    }}/*** 商品*/class Hamburger{    int id;    public Hamburger(int id) {        this.id = id;    }}

其中的核心有这么几点:

  1. 容器相当于一个栈,是后进先出的;
  2. 容器的两个方法对于资源的操作,一个和生产者交互,一个和消费者交互,除了 synchronized 修饰,因为两个方法是互斥的,所以利用 wait 和 notify 方法使他们完成阻塞和解除阻塞;
  3. 生产者和容器交互,添加数据;
  4. 消费者和容器交互,删除数据。

前面关于 线程的阻塞问题,生命周期里的阻塞 ,完整的可能情况,就包含这里的阻塞情况:

四、信号灯法实现

和上一种通过容器的容量让线程之间互相通知的方法不同,信号灯法没有用数据缓存的方式,而是用 信号灯来指示双方 ,对方是否已经准备好了要和你通信。

下面是一个 电视直播和观众的代码示例,通过信号灯,通知演员和观众直播,确保演员在演的时候,让观众来看。

/*** 协作模型:生产者消费者实现:信号灯法*/public class Cooperation2 {    public static void main(String[] args) {        TV tv = new TV();        new Actor(tv).start();        new Fans(tv).start();    }}/*** 生产者:演员*/class Actor extends Thread{    TV tv;    public Actor(TV tv){        this.tv = tv;    }    @Override    public void run() {        for (int i=0; i<10; i++){            if (i%2 == 0){                this.tv.play("节目 " + i);            }else{                this.tv.play("广告 " + i);            }        }    }}/*** 消费者:观众*/class Fans extends Thread{    TV tv;    public Fans(TV tv){        this.tv = tv;    }    @Override    public void run() {        for (int i=0; i<10; i++){            tv.watch();        }    }}/*** 共同资源:电视直播*/class TV{    String voice;    //信号灯,如果为真则演员准备,观众等待    //如果为假,则观众就位,演员等待    boolean flag = true;    //表演方法:针对生产者    public synchronized void play(String voice){        //演员等待        if (!flag){            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        this.voice = voice;        System.out.println("表演 "+voice +" ing");        //唤醒观众        this.notifyAll();        this.flag = !flag;    }    //观看方法:针对消费者    public synchronized void watch(){        //观众等待        if (flag){            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println("观看 " + voice +" ing");        this.notifyAll();        this.flag = !flag;    }}

可以看到,相比管程法的核心区别是:

TV 没有用一个容器存储数据 ,只是通过生产者是否生产,来决定 信号灯 的标志,以此 通知消费者来消费。

显然这两种实现方法,有不同的适用场景,那就是决定于生产者消费者是否有数据沟通。

java 生产者消费者_Java多线程:线程间通信—生产者消费者模型相关推荐

  1. 多线程-线程间通信-多生产者多消费者示例

    1.多线程-线程间通信-多生产者多消费者问题 多生产者和多消费者.等待唤醒机制. 产生了两个问题: 1.出现了多次连续生产,未消费,或者一个商品被消费多次. 解决:必须要--------每一个被唤醒的 ...

  2. Java基础学习——多线程(线程间通信-生产者消费者代码示例)

    JDK 1.5提供了多线程升级方案 将同步synchronized替换成了显示的Lock操作.可以实现唤醒.冻结指定的线程. Lock接口 Lock 实现提供了比使用 synchronized 方法和 ...

  3. java消费者生产者设计模式_java 多线程并发设计模式之四: 生产者消费者模式

    生产者消费者模式是一个经典的多线程设计模式,其核心思想是:有两类线程和一个内存缓冲区或者队列, 一类线程发起任务,并提交到队列中.另一类线程用来处理这些任务,叫做消费者线程. 这两类线程进行通信的桥梁 ...

  4. Java并发编程(04):线程间通信,等待/通知机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...

  5. java同步通信方式_java多线程同步与通信示例(synchronized方式)

    java多线程同步示例,来自<疯狂java讲义>.通过synchronized,wait(),notify(),notifyAll()实现多线程同步与通信.假设现在系统中有两个线程,这两个 ...

  6. 【Java 并发编程】多线程、线程同步、死锁、线程间通信(生产者消费者模型)、可重入锁、线程池

    并发编程(Concurrent Programming) 进程(Process).线程(Thread).线程的串行 多线程 多线程的原理 多线程的优缺点 Java并发编程 默认线程 开启新线程 `Ru ...

  7. Java多线程编程-(4)-线程间通信机制的介绍与使用

    上一篇: Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-线程本地Th ...

  8. Java进阶知识 - 多线程与线程间通信

    CountdownLatch, CyclicBarrier 分别适合什么场景呢? 大部分情况下, 子线程只需要关心自身执行的任务. 但在某些复杂的情况下, 需要使用多个线程来协同完成某个任务, 这就涉 ...

  9. android线程间通信的几种方法_Android线程间通信机制

    讲解Handler机制的博文很多,我也看了很多,但说实话,在我对Handler几乎不怎么了解的情况下,每一篇文章我都没太看懂,看完之后脑子里还是充满了疑问.究其原因,是因为几乎每一篇文章一上来就开始深 ...

最新文章

  1. MySQL 性能优化技巧(一)
  2. iOS 证书相关概念
  3. 阿里云 centos ssh key 客户端 无密码登录 ssh 登录
  4. 知乎万赞回答!如何在一周内快速摸清一个行业?
  5. FPGA基础之HLS
  6. CSS表格与浮动定位
  7. Docker--(三)--测试
  8. 深度学习基础(九)—— 稀疏编码(sparse coding)
  9. JS函数自定义弹窗;纯JS实现弹窗
  10. 计算机网络与物流论文题目,电子商务与现代物流关系浅析毕业论文.docx
  11. 商品销售信息管理系统(大一C语言课设)
  12. linux忘记root密码VMware-centos6.8演示
  13. ICP波长及分析校正
  14. C#,茅塞顿开的精致好码,通用型科学计算器的源代码
  15. CodeBlocks使用方法
  16. 戴文的Linux内核专题:03 驱动程序
  17. MCC(移动国家码)和 MNC(移动网络码
  18. 嵌入式研发精英培养计划课程体系-曹国辉-专题视频课程
  19. gnuplot使用小教程
  20. 聚划算客户端2期总结

热门文章

  1. 单体应用架构和微服务架构的区别
  2. 企业生产环境数据库备份锁表问题
  3. 修改mysql root的秘密
  4. OSChina 周日乱弹 ——程序员被辞退的理由
  5. android-tv
  6. DataSet DataTable DataReader DataAdapter之间的区别
  7. asp.net调用ajax实例
  8. 10-05 Java 内部类概述和讲解
  9. Exchange Server 2013系列四:小企业邮件系统部署
  10. IDF 实验室 初探乾坤