生产者消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。
线程的等待和通知
一旦线程进入同步块或同步方法,JVM会启动监视器监控线程的状态,线程都会持有锁,同步块持有是锁对象,同步方法的锁是this.
Object类的方法:
- void wait() 让持有锁的线程进入等待状态,直到被通知
- void wait(long time) 让线程等待,直到被通知或时间结束
- void notify() 随机选择一个等待的线程,进行通知
- void notifyAll() 通知所有等待的线程
注意:上面的方法只能是锁对象调用,否则出现异常IllegalMonitorStateException
案例代码
以做包子为例,假设仓库里最多能放100个包子,这个保存包子的仓库可以看做缓冲区,生产者做好包子后会放到仓库里,消费者则会从仓库中取出包子吃掉,当生产者做包子
太快,会把仓库装满,生产者就会等待;而消费者吃包子太快,仓库被取空了,消费者就会等待。
/**
* 包子
*
*/
public class Baozi {
//包子编号
private int id;
public Baozi(int id){
this.id = id;
}
public String toString(){
return "包子#"+id;
}
}
/**
* 包子仓库
* @author xray
*
*/
public class BaoziStore {
//最大数量
public static final int MAX_NUM = 100;
//包子集合
private LinkedList<Baozi> baozis = new LinkedList<>();
//锁对象
private Object lock = new Object();
/**
* 做包子
*/
public void makeBaozi(){
//使用同步块
synchronized (lock) {
//判断包子数量是否达到最大值
if(baozis.size() == MAX_NUM){
System.out.println("仓库满了,生产者等待:"+Thread.currentThread().getName());
try {
//让生产者等待
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//如果仓库还有空间,让生产者继续生产
System.out.println("仓库有空了,生产者继续做包子:"+Thread.currentThread().getName());
lock.notifyAll();
}
//创建包子,添加到集合中
Baozi baozi = new Baozi(baozis.size());
baozis.push(baozi);
System.out.println(Thread.currentThread().getName()+"生产者,做了"+baozi);
}
}
/**
* 吃包子
*/
public void takeBaozi(){
synchronized (lock) {
//判断如果仓库为空,就让消费者等待
if(baozis.size() == 0){
System.out.println("仓库空了,消费者等一下:"+Thread.currentThread().getName());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("仓库有包子了,消费者快来:"+Thread.currentThread().getName());
//如果仓库有包子,就通知消费者来吃
lock.notifyAll();
}
//从栈顶拿包子,然后删除掉
if(baozis.size() > 0){
Baozi baozi = baozis.pop();
System.out.println(Thread.currentThread().getName()+"消费者吃掉了"+baozi);
}
}
}
}
public class TestBaoziStore {
public static void main(String[] args) {
//仓库对象
BaoziStore store = new BaoziStore();
//创建生产者线程,生产100个包子
Thread productor = new Thread(()->{
for(int i = 0;i < 100;i++){
store.makeBaozi();
}
});
//启动生产者
productor.start();
//创建10个消费者,每个吃10个包子
for(int i = 0;i < 10;i++){
Thread consumer = new Thread(()->{
for(int j = 0;j < 10;j++){
store.takeBaozi();
}
});
consumer.start();
}
}
}
上面的生产者消费者模式除了使用锁的等待和通知方式实现外,还可以使用阻塞队列,自动完成等待和通知功能。
阻塞队列介绍
阻塞队列在java.util.concurrent(并发包)中,用于解决大并发量情况下,数据的生产和消费速度不一致问题。
主要API:
- BlockingQueue接口
- void put(T t) 添加数据到末尾,队列满了会自动阻塞线程
- T take() 从队列的头部取出数据并删除,队列空了会阻塞线程
BlockingQueue的主要实现类
- ArrayBlockingQueue 数组阻塞队列
- LinkedBlockingQueue 链表阻塞队列
案例代码
public class TestBaozi {
public static void main(String[] args) {
//创建链表阻塞队列
LinkedBlockingQueue<Baozi> baozis = new LinkedBlockingQueue<>(10);
//创建生产者线程,生产100个包子
Thread productor = new Thread(()->{
for(int i = 0;i < 100;i++){
try {
//创建包子,存入集合
Baozi baozi = new Baozi(i);
baozis.put(baozi);
System.out.println(Thread.currentThread().getName()+"生产了"+baozi);
} catch (Exception e) {
e.printStackTrace();
}
}
});
productor.setName("生产者");
productor.start();
//创建5个消费者,每个吃20个包子
for(int i = 0;i < 5;i++){
Thread consumer = new Thread(()->{
for(int j = 0;j < 20;j++){
try {
//从集合拿包子
Baozi baozi = baozis.take();
System.out.println(Thread.currentThread().getName()+"吃掉了"+baozi);
} catch (Exception e) {
e.printStackTrace();
}
}
});
consumer.setName("消费者"+i);
consumer.start();
}
}
}
结束
大家如果需要学习其他Java知识点,戳这里 超详细的Java知识点汇总