生产者和消费者模型
线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,各个线程必须要能够通信,从而完成工作。线程通信中的经典问题:生产者和消费者问题
模型:
这个模型也体现了面向对象的设计理念:低耦合
也就是为什么生产者生产的东西为什么不直接给消费者,还有经过一个缓冲区(共享资源区)
这就相当于去包子店吃包子,你要5个包子,老板把5个人包子放在一个盘子中再给你,这个盘子就是一个缓冲区。
现在模拟一个生产者和消费者模型
生产者生产“李小龙 男”,消费者消费也就是打印出“李小龙 男”
生产者生产“狗晗 女”,消费者消费也就是打印出“狗晗 女”
要求生产者生产一个,消费者消费一个。也就是缓冲区为1
期望结果:
李小龙 男
狗晗 女
李小龙 男
狗晗 女
···
解决问题
需要创建4个类,分别是生产者,消费者,共享资源类,测试类
生产者类实现Runnable接口,覆盖run方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Producer implements Runnable{
ShareResource resource = null;
public Producer(ShareResource resource){
this.resource = resource;
}
public void run() {
for(int i = 0; i < 50;i++){
if(i % 2 == 0){
resource.product("李小龙", "男");
}else{
resource.product("狗晗","女");
}
}
}
}
消费者类实现Runnable接口,覆盖run方法
1
2
3
4
5
6
7
8
9
10
11public 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();
}
}
}
共享资源类有生产者和消费者两个类的引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public 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);
}
}
测试类可以启动线程
1
2
3
4
5
6
7public 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方法:唤醒在等待池中等待的所有线程,把线程转移到锁池中
等待池:没有机会获取同步锁,只能等待被唤醒,被转移到锁池中。
锁池: 当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁.,当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权.
又来看这个模型,应该得定义一个判断当前共享资源区是否为空,如果为空,消费者就应该等待生产者生产,当生产者生产 完毕后应该唤醒消费者进行消费。如果不为空,生产者就应该等待消费者,等消费者完毕后再唤醒生产者继续生产
1 | public class ShareResource { |
用synchronized修饰方法,会自动获取锁,自动释放锁。从java5开始使用Lock机制取代synchronized代码块和synchronized方法,使用Condition接口对象的await,signal,signalAll方法取代notify,notifyAll,用法还是大同小异
1 | public class ShareResource { |
结果就是:
Lock机制
创建一个Lock接口实现类ReetranLock的对象,进入同步方法后立即加锁,lock.lock();最后释放锁,看API中的典型代码