Java筑基之线程并发生产消费者模式

线程并发协作(生产者/消费者模式)

在这里插入图片描述

  • 生产者

负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。

  • 消费者

负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。

  • 仓库

消费者不能直接使用生产者的数据,它们之间有个“仓库”。生产者将生产好的数据放入“仓库”,消费者从“仓库”拿要处理的数据

  • 注意:
  1. 仓库作为消费者与生产者之间的缓冲区,使生产与消费的线程分离大道解耦的效果
  2. 解决生产消费过载,生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据
实例需求
  • 《wait/notify实现生产者和消费者程序》
  • 采用多线程技术,例如wait/notify,设计实现一个符合生产者和消费者问题的程序,
  • 对某一个对象(枪膛)进行操作,其最大容量是10颗子弹,
  • 生产者线程是一个压入线程,它不断向枪膛中压入子弹,
  • 消费者线程是一个射出线程,它不断从枪膛中射出子弹。
/**
 * @author LHW
 * @date 2020/12/25.
 * Description:子弹实体类
 */
public class Bullet {
    public int id;
}

/**
 * @author LHW
 * @date 2020/12/25.
 * Description:枪膛 压入发射子弹
 */
public class Clip {
    public int index = 0;//压入与发射的标志位
    public Bullet[] bullets = new Bullet[10];//缓存子弹弹夹

    //压入子弹入枪膛
    public synchronized void pushBullet(Bullet bullet) {
        try {
            while (index == bullets.length) {
                System.out.println("pushBullet 弹夹已满进入等待。。。。。index=" + index);
                this.wait();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.notify();//唤醒等待线程
        bullets[index] = bullet;
        index++;
    }

    //枪膛射出子弹
    public synchronized Bullet shootBullet() {

        try {
            while (index == 0) {
                System.out.println("shootBullet 枪膛为空进入等待。。。。。index=" + index);
                wait();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.notify();
        index--;
        return bullets[index];
    }
}

/**
 * @author LHW
 * @date 2020/12/25.
 * Description:
 * 《wait/notify实现生产者和消费者程序》
 * 采用多线程技术,例如wait/notify,设计实现一个符合生产者和消费者问题的程序,
 * 对某一个对象(枪膛)进行操作,其最大容量是10颗子弹,
 * 生产者线程是一个压入线程,它不断向枪膛中压入子弹,
 * 消费者线程是一个射出线程,它不断从枪膛中射出子弹。
 */
public class ShootingMain {
    public static void main(String[] args) {
        Clip mClip = new Clip();
        new PushThread(mClip).start();
        new ShootThread(mClip).start();
    }

    static class PushThread extends Thread {
        Clip mClip;

        PushThread(Clip clip) {
            mClip = clip;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < mClip.bullets.length; i++) {
                Bullet bullet = new Bullet();
                bullet.id = i;
                mClip.pushBullet(bullet);
                System.out.println("PushThread 子弹压入枪膛。。。" + bullet.id);
                //如果这里不执行睡眠等待 生产会压入所有子弹后执行消费线程的射出子弹
                SleepTools.ms(1000);//睡眠一秒 阻塞生产线程 执行消费线程
            }
        }
    }

    static class ShootThread extends Thread {
        Clip mClip;

        ShootThread(Clip clip) {
            mClip = clip;
        }

        @Override
        public void run() {
            super.run();
            for (int i = 0; i < mClip.bullets.length; i++) {
                Bullet bullet = mClip.shootBullet();
                System.out.println("ShootThread 子弹射出枪膛。。。" + bullet.id);
                SleepTools.ms(1000);//睡眠一秒 阻塞消费线程 执行生产线程

            }
        }
    }
}

结果输出

PushThread 子弹压入枪膛。。。0
ShootThread 子弹射出枪膛。。。0
shootBullet 枪膛为空进入等待。。。。。index=0
PushThread 子弹压入枪膛。。。1
ShootThread 子弹射出枪膛。。。1
shootBullet 枪膛为空进入等待。。。。。index=0
PushThread 子弹压入枪膛。。。2
ShootThread 子弹射出枪膛。。。2
PushThread 子弹压入枪膛。。。3
ShootThread 子弹射出枪膛。。。3
PushThread 子弹压入枪膛。。。4
ShootThread 子弹射出枪膛。。。4
shootBullet 枪膛为空进入等待。。。。。index=0
PushThread 子弹压入枪膛。。。5
ShootThread 子弹射出枪膛。。。5
PushThread 子弹压入枪膛。。。6
ShootThread 子弹射出枪膛。。。6
shootBullet 枪膛为空进入等待。。。。。index=0
PushThread 子弹压入枪膛。。。7
ShootThread 子弹射出枪膛。。。7
shootBullet 枪膛为空进入等待。。。。。index=0
PushThread 子弹压入枪膛。。。8
ShootThread 子弹射出枪膛。。。8
PushThread 子弹压入枪膛。。。9
ShootThread 子弹射出枪膛。。。9

总结
消费者/生产模式通常用于如下情景:
  • 1.生产与消费共享同一个资源,并且生产者和消费者之间相互依赖,互为条件
  • 2.生产者没有生产产品前即资源为空时,消费者进入等待,否则通知消费者进行消费
  • 3.消费者消费后要通知生产者已消费完,需要通知生产者进行生产产品以供消费
线程直接的协作
- 协作(等待 / 通知机制)情景

是指一个线程 A 调用了对象 User 的 wait()方法进入等待状态,而另一个线程 B
调用了对象User 的 notify()或者 notifyAll()方法,线程A 收到通知后从对象User 的wait()方法返回,进而执行后续操作。上述两个线程通过对象User来完成交互,而对象上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

- notify():
  • 通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程
    获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。
- notifyAll():

通知所有等待在该对象上的线程

- wait()

调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断
才会返回.需要注意,调用 wait()方法后,会释放对象的锁

- wait(long)

超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有
通知就超时返回

- wait (long,int)

-对于超时时间更细粒度的控制,可以达到纳秒

注意:

以上方法均是java.lang.Object类的方法;
都只能在同步方法或者同步代码块中使用,否则会抛出异常

等待和通知的标准范式
等待方遵循如下原则
  1. 获取对象的锁
  2. 如果条件不满足,那么调用的对象的wait()方法,被通知后仍要检查条件
  3. 条件满足则执行对应的逻辑
  synchronized(对象){
      while(条件不满足){
          对象.wait();
      }
      对应的处理逻辑
  }
通知方遵循如下原则
  1. 获取对象的锁
  2. 改变条件
  3. 通知所有等待在对象上的线程
  synchronized(对象){
      改变条件
          对象.notifyAll();
     
  }

在调用 wait () 、notify() 系列方法 之前,线程必须要获得该对象的对象级
别锁,即只能在同步方法或同步块中调用 wait ()方法 、notify() 系列方法
,进
入 wait()方法后,当前线程释放锁,在从 wait()返回前,线程与其他线程竞
争重新获得锁,执行 notify()系列方法的线程退出调用了 notifyAll 的 synchronized
代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会
继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的
线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕

注意事项:
1.notify和notifyAll应该用谁?
  • 尽可能用 notifyall(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我
    们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程
2.调用 yield() 、sleep()、wait()、notify()等方法对锁有何影响?
  • yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
  • 调用 wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新
    去竞争锁,锁竞争到后才会执行 wait 方法后面的代码。
  • 调用 notify()系列方法后,对锁无影响,线程只有在 syn 同步代码执行完后才
    会自然而然的释放锁,所以 notify()系列方法一般都是 syn 同步代码的最后一行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值