赞
踩
多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步。
另一个问题是, 多个线程之间如何协作呢 ?
我们看一个仓库出货问题(更具体一些,快餐店直接放好炸货的架子,不过每次只放一份)
这其实就是一个线程同步问题。 生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖,互为条件。
如果一个快餐店:
先点单,餐出来之后再收钱。这种模式叫BIO-阻塞IO模式。
如果一个快餐店:
先收钱,收完钱消费者在旁边等。这种就是生产者-消费者模式。
这类问题里,同步的候只有 synchronized 是不够的,因为他虽然能解决资源的共享问题,实现资源的同步更新,但是无法 在不同线程之间进行消息传递 (通信)。
所以只有我们之前所说的 加锁 和 排队 是不够的,还要有 通知 。
生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加产品,消费者从存储空间中取走产品,当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。
为了解决双方能力不等而等待的问题,引入对应的解决方案。生产者消费者模型是一种并发协作模型。
生产者和消费者都是通过缓冲区进行数据的 放 和 那 。
这样的话,一来可以避免旱的旱死,涝的涝死的问题:不管哪一方过快或者过慢,缓冲区始终有一部分数据;二来能够达到生产者和消费者的解耦,不再直接通信,从而提高效率。
因为容器相当于一个输送商品的管道,所以成为 管程法 。
采用类似红灯绿灯的模式,决定车走还是人走。
jdk 里面 Object 类老早就有提供解决线程间通信的问题的方法:
这几个方法都是在 同步方法或者同步代码块 中使用,否则会抛出异常。
(很多面试题问 Java 的 Object 类有哪些方法,都是希望得到关于这块的答案,引导多线程)
管程法实现的四个角色:
利用 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;
- }
- }
其中的核心有这么几点:
前面关于 线程的阻塞问题,生命周期里的阻塞 ,完整的可能情况,就包含这里的阻塞情况:
和上一种通过容器的容量让线程之间互相通知的方法不同,信号灯法没有用数据缓存的方式,而是用 信号灯来指示双方 ,对方是否已经准备好了要和你通信。
下面是一个 电视直播和观众的代码示例,通过信号灯,通知演员和观众直播,确保演员在演的时候,让观众来看。
- /**
- * 协作模型:生产者消费者实现:信号灯法
- */
- 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 没有用一个容器存储数据 ,只是通过生产者是否生产,来决定 信号灯 的标志,以此 通知消费者来消费。
显然这两种实现方法,有不同的适用场景,那就是决定于生产者消费者是否有数据沟通。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。