1、生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费;
- 如果仓库中没有产品,生产者将产品放入仓库;如果仓库中有产品,停止生产并等待,直到仓库中的产品被消费者取走为止;
- 如果仓库中放有产品,则消费者将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
对于生产者,没有生产产品之前,要通知消费者等待;而生产了产品之后,又需要马上通知消费者消费;
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费;
在生产者消费者问题中 , 仅有 synchronized 是不够的,synchronized 可阻止并发更新同一个共享资源 , 实现了同步;但是 synchronized 不能用来实现不同线程之间的消息传递(通信)。这里要用到
wait()
和notify()
的相关方法。
Java 提供了几个方法解决线程之间的通信问题,注意:这些方法均是 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常 IllegalMonitorStateException。
方法名 | 作用 |
---|---|
wait() |
表示线程一直等待,直到其他线程通知,与 sleep() 不同,会释放锁。 |
wait(long timeout) |
指定等待的毫秒数 |
notify() |
唤醒一个处于等待状态的线程 |
notifyAll() |
唤醒同一个对象上所有调用wait() 方法的线程,优先级别高的线程优先调度。 |
生产者消费者 demo
1 | // 测试:生产者消费者模式 |
2、传统的生产者消费者问题(虚假唤醒)
1 | /** |
存在的问题:以上程序中只使用了A、B两个线程的话没有问题,但是如果使用四个甚至八个线程的话仍然会出现线程不安全的问题。这种情况叫做虚假唤醒。
虚假唤醒:当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功。 比如买货,如果商店本来没有货物,突然新增一件商品,此时所有的线程都被唤醒了 ,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁。
解决方法:等待应该总是出现在循环 while
中,而不是 if
中,因为 while 会一直进行判断,直到条件不满足才会继续向下执行,不使用一次性判断,就可以避免产生虚假唤醒的问题。
3、JUC 版的生产者消费者问题
传统三件套 | synchronized | wait() |
notifyAll() |
---|---|---|---|
JUC | Lock | **await() **(Condition) |
signal() |
Lock 可以替换 synchronized 方法和语句的使用,Condition 取代了对象监视器方法的使用,原理相同,仍可以实现生产者消费者问题。
1 | /** |
问题:虽然实现了功能,但是线程的执行是随机的,和之前传统方式的解决效果是一样的,没什么区别。
如何让 ABCD 四个线程按顺序执行?
使用 Condition 精准的通知和唤醒线程,这是通过 JUC 的方式解决生产者消费者问题的一个优势。
1 | /** |
4、使用阻塞队列实现生产者消费者问题
实现一个通过命令行读取货物的运输时间的生产者线程和3个快递员的消费者线程,当等待投递的货物超过3个时,生产者线程拒绝下一个货物的输入。如,用户输入1000,表示这个货物需要花费1000毫秒投递,如果有快递员线程空闲,则进行投递,并消耗相应时间。
货物实体类
1 | public class Goods { |
生产者和消费者接口
1 | public interface IProducer { |
1 | public interface IConsumer { |
生产者和消费者
1 |
|
1 |
|
Solution
1 |
|
-------------Thanks for your time.-------------