生产者消费者模型是多线程中最常问的知识点,而线程间的通信也是多线程中的重点,自己学习看了《Java并发编程的艺术》一书并看了很多博客后也学习总结出自己的学习心得供大家参考。
生产者消费者模型
一、线程间通信基础概念
1.什么是线程间通信
总所周知,Java语言是多线程编程的,每个线程再开始运行时都拥有自己的栈空间,都会按照既定的代码一步步的执行结束。但是,对于每一个运行中的线程,如果仅仅时孤立的运行那么价值很小,如果多个线程能配合完成工作,那么就会有很高的效率。所以就有了线程间通信,表示多个线程之间可以通过某种机制达到交流配合的作用。
2.synchronized和volatile关键字
对于每个线程,都拥有自己的本地内存,而所有线程都共享一块内存。虽然对象和成员变量是在共享内存中分配的,但每一个执行的线程还是会拥有一份拷贝,这样做虽然可以加速程序运行,但同时也导致了一个线程看到的变量并不一定是最新的。
synchronized关键字:用来修饰方法或者以同步块形式使用,具体可参见——>初识synchronized关键字 ,使用synchronized关键字主要确保多线程再同一时刻只能由一个线程获取到锁进入同步方法或同步代码块中,它保证了所有线程对变量的可见性和排他性。
volatile关键字:用来修饰字段,它保证了任何程序对该变量的读取必须从共享内存中获取,而对该变量的修改也必须同步刷新到共享内存中。它保证了所有线程对变量访问的可见性。同时volatile是轻量级的synchronized,它比synchronized使用和执行的成本更低,因为使用时不会导致上下文切换。
3.等待/通知机制
一个线程修改了一个对象的值,而另一个线程感知到了变化后,进行相应的操作,这种操作始于一个线程终止于另一个线程,二者之间的交流就使用等待通知机制。
先来了解我们常用的方法:
方法名称 | 方法描述 |
---|---|
wait() | 在一个线程中调用某一个对象的该方法进入WAITING状态,并释放锁,只有等待其它线程通知或被中断后才返回 |
wait(long) | 进入等待状态后,超过一段时间没有通知就超时返回 |
notify() | 调用一个对象的该方法,通知再该对象上等待的线程再次获取到对象锁后返回 |
notifyAll() | 通知所有等待在该对象上的线程返回 |
这里注意,这些方法都是Object类中的方法,为什么线程间得操作却不是Thread类中的方法呢?
原因是:通过synchronized锁住的可以是任意对象,只用调用同一个对象的notify()方法才能唤醒该对象的wait()方法中的线程,也即等待和唤醒必须使用的同一个锁。
等待/通知机制就是指一个线程A调用了对象O的wait()
方法进入等待,而另一个线程B调用了O的notify()
或者notifyAll()
方法,在线程B释放了该对象锁后,线程A重新获取锁从wati()方法返回继续执行。(注意,一定是B释放锁后A才能返回继续执行!!!)
4. wait()和notift()方法的执行
参考下图:
调用wait()
方法后,线程进入对象的等待队列处于等待状态,线程不具备竞争锁的能力;调用notify()
方法后,线程由等待队列转移到同步队列,处于阻塞状态,可与其他线程共同竞争该锁。
二、生产者消费者模型
1.什么是生产者消费者模型
就像我们刚才所说,一个线程修改变量,另一个线程感知变化并作出相应的操作,前者就可以称之为生产者,后者即消费者。在实际场景中,生产者专门负责生产,而消费者则负责消费,二者中还有一个缓冲区来存放共同操作的数据。生产者放数据,消费者取数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
2.为什么使用生产者消费者模型
- 解耦
如果将生产者消费者均设置为类,那么在使用时,就可能会让生产者直接调用消费者中的某个函数,这样二者之间就产生了耦合,消费者类中的代码修改就会影响到生产者。而如果使用缓冲区,二者之间就没有直接的依赖,也就降低了耦合。 - 支持并发
生产者如果直接调用消费者的某一个方法,如果消费者处理数据很慢,则生产者就会阻塞而浪费时间,而使用多线程下的生产者消费者模型后,二者时独立的个体,生产者消费者只做自己的事情,不需要依赖对方的处理速度。 - 支持忙闲不匀
有了缓冲区后,如果生产者制造数据时快时慢时,当制造快时,消费者来不及处理,就可以先将数据放入缓冲区,等待消费者慢慢处理。
3.代码实现
根据上述讲解,实现一个生产者消费者的模型,生产者和消费者共同对共享变量COUNT进行操作。当COUNT >= 10时停止生产,当COUNT = 0时停止消费。
public class