生产者和消费者模型
线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,各个线程必须要能够通信,从而完成工作。线程通信中的经典问题:生产者和消费者问题
模型:
这个模型也体现了面向对象的设计理念:低耦合
也就是为什么生产者生产的东西为什么不直接给消费者,还有经过一个缓冲区(共享资源区)
这就相当于去包子店吃包子,你要5个包子,老板把5个人包子放在一个盘子中再给你,这个盘子就是一个缓冲区。
现在模拟一个生产者和消费者模型
生产者生产“李小龙 男”,消费者消费也就是打印出“李小龙 男”
生产者生产“狗晗 女”,消费者消费也就是打印出“狗晗 女”
要求生产者生产一个,消费者消费一个。也就是缓冲区为1
-
期望结果:
李小龙 男
狗晗 女
李小龙 男
狗晗 女
···
解决问题
需要创建4个类,分别是生产者,消费者,共享资源类,测试类
-
生产者类实现Runnable接口,覆盖run方法
public class Producer implements Runnable{ ShareResource resource = null; public Producer(ShareResource resource){ this.resource = resource; } @Override public void run() { for(int i = 0; i < 50;i++){ if(i % 2 == 0){ resource.product("李小龙", "男"); }else{ resource.product("狗晗","女"); } } } }
-
消费者类实现Runnable接口,覆盖run方法
public class Consumer implements Runnable { ShareResource resource = null; public Consumer(ShareResource resource){ this.resource = resource; } public void run(){ for(int i = 0;i < 50;i++){ resource.consume(); } } }
-
共享资源类有生产者和消费者两个类的引用
public class ShareResource { private String name; private String sex; public void product(String name,String sex){ this.name = name; try{ Thread.sleep(10); }catch(Exception e){ e.printStackTrace(); } this.sex = sex; } public void consume(){ try{ Thread.sleep(10); }catch(Exception e){ e.printStackTrace(); } System.out.println(this.name + " " + this.sex); } }
-
测试类可以启动线程
public class Demo { public static void main(String[]args){ ShareResource resource = new ShareResource(); new Thread(new Producer(resource)).start(); new Thread(new Consumer(resource)).start(); } }
运行后发现,结果性别发生了紊乱:
我靠,这我龙哥愿意吗,怎么回事?
就是因为多线程并发同一资源,要把它们的方法用synchronized修饰保证它们生产和消费不同时进行,当你加上synchronized之后:
确实性别紊乱的问题是解决了,狗晗是高兴了,但是它们并不是交替出现的啊,怎么回事?
你要交替出现,必须一个等着一个啊,你生产完了,你得停下来休息啊,让消费者先消费,等到消费者消费完之后,让消费者把你叫醒再继续生产,消费者就又去休息去了,这个消费者优点类似于吃了睡,睡了在吃一样。
wait和notify方法
wait方法:执行该方法的对象释放同步锁,JVM把该线程存放到等待池中,等待其他线程唤醒
notify方法:唤醒在等待池中的等待的任意一个线程,把线程转移到锁池中
-
notifyAll方法:唤醒在等待池中等待的所有线程,把线程转移到锁池中
等待池:没有机会获取同步锁,只能等待被唤醒,被转移到锁池中。
锁池: 当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁.,当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权.
又来看这个模型,应该得定义一个判断当前共享资源区是否为空,如果为空,消费者就应该等待生产者生产,当生产者生产 完毕后应该唤醒消费者进行消费。如果不为空,生产者就应该等待消费者,等消费者完毕后再唤醒生产者继续生产
public class ShareResource {
private String name;
private String sex;
private boolean isEmpty = true;//判断共享区空和满的一个标志
//生产者生产
synchronized public void product(String name,String sex){
try {
while(!isEmpty){//共享区不空,生产者需要停下等待消费者消费
/**
* this是同步锁(同步监听对象),wait方法是Object类中 的方法,调用该方法就释放同步锁
* 然后JVM把该线程存放到等待池中,等待其他线程唤醒该线程
* 该方法只能被同步监听对象调用,否则报错IllegalMonitorStateException.
*/
this.wait();
}
//----------开始生产-----------
this.name = name;
Thread.sleep(10);
this.sex = sex;
//---------结束生产------------
isEmpty = false; //设置共享区为不空
/**
* notify方法是唤醒等待池中的随机一个线程,把线程转移到锁池中等待(锁池中有机会获取到锁)
* notifyAll方法是唤醒等待池中的所有线程,把线程转移到锁池中等待
* 该方法只能被同步监听对象锁调用,否则报错
*/
this.notify(); //唤醒一个消费者 notify是唤醒其中一个,notifyAll是唤醒全部
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费者消费
synchronized public void consume(){
try {
while(isEmpty){//当共享区为空时,消费者进行等待
this.wait();
}
//------------开始消费-------------
Thread.sleep(30);
System.out.println(name + " " + sex);
//------------结束消费-------------
isEmpty = true;
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用synchronized修饰方法,会自动获取锁,自动释放锁。从java5开始使用Lock机制取代synchronized代码块和synchronized方法,使用Condition接口对象的await,signal,signalAll方法取代notify,notifyAll,用法还是大同小异
public class ShareResource {
private String name;
private String sex;
private boolean isEmpty = true;
private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void product(String name,String sex){
lock.lock();//获取锁
try {
while(!isEmpty){//不空,生产者要等待消费者消费
condition.await();//等待,相当于this.wait();
}
this.name = name;
Thread.sleep(10);
this.sex = sex;
isEmpty = false;//把共享区置为不空
condition.signal();//唤醒一个线程,相当于this.notify(). signalAll 可以唤醒全部线程
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//释放锁
}
}
public void consume(){
lock.lock();
try {
while(isEmpty){//共享区为空,需要等待生产者生产
condition.await();
}
Thread.sleep(20);
System.out.println(this.name + " " + this.sex);
isEmpty = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
结果就是:
Lock机制
创建一个Lock接口实现类ReetranLock的对象,进入同步方法后立即加锁,lock.lock();最后释放锁,看API中的典型代码