Java 生产者消费者模式详细分析(2)

wait()系列的三个方法局限性很大,因为无论是睡眠还是唤醒的动作,都完全和锁耦合在一起了。例如,锁obj1关联的线程只能唤醒obj1线程池中的线程,而无法唤醒锁obj2关联的线程;再例如,在原来synchronized同步时,锁是在开始同步时隐式地自动获取的,且是在执行完一整个任务后,又隐式地自动释放锁,也就是说获取锁和释放锁的动作无法人为控制。

从JDK 1.5开始,Java提供了java.util.concurrent.locks包,这个包中提供了Lock接口、Condition接口和ReadWriteLock接口,前两个接口将锁和监视器方法(睡眠、唤醒操作)解耦了。其中Lock接口只提供锁,通过锁方法newConditon()可以生成一个或多个与该锁关联的监视器,每个监视器都有自己的睡眠、唤醒方法。也就是说Lock替代了synchronized方法和同步代码块的使用,Condition替代了Object监视器方法的使用。

如下图:

Java 生产者消费者模式详细分析

当某线程执行condition1.await()时,该线程将进入condition1监视器对应的线程池睡眠,当执行condition1.signal()时,将随机唤醒condition1线程池中的任意一个线程,当执行condition1.signalAll()时,将唤醒condition1线程池中的所有线程。同理,对于condition2监视器也是一样的。

即使有多个监视器,但只要它们关联的是同一个锁对象,就可以跨监视器操作对方线程。例如condition1中的线程可以执行condition2.signal()来唤醒condition2线程池中的某个线程。

要使用这种锁、监视器的关联方式,参考如下步骤:

import java.util.concurrent.locks.*; Lock l = new ReentrantLock(); Condition con1 = l.newCondition(); condition con2 = l.newCondition(); l.lock(); try{ //包含await()、signal()或signalAll()的代码段... } finally { l.unlock(); //由于代码段可能异常,但unlock()是必须执行的,所以必须使用try,且将unlock()放进finally段 }

具体用法见后文关于Lock、condition的示例代码。

3.单生产者单消费者模式

一个生产者线程,一个消费者线程,生产者每生产一个面包放进盘子里,消费者从盘子里取出面包进行消费。其中生产者判断是否继续生产的依据是盘子里没有面包,而消费者判断是否消费的依据是盘子里有面包。由于这个模式中,盘子一直只放一个面包,因此可以把盘子省略掉,生产者和消费者直接手把手地交递面包即可。

首先需要描述这三个类,一是多线程共同操作的资源(此处即面包),二是生产者,三是消费者。在下面的例子中,我把生产面包和消费面包的方法分别封装到了生产者和消费者类中,如果把它们封装在面包类中则更容易理解。

//描述资源:面包的名称和编号,由编号决定面包的号码 class Bread { public String name; public int count = 1; public boolean flag = false; //该标记为wait()和notify()��供判断标记 } //生产者和消费者先后处理的面包资源是同一个,要确保这一点, //可以按单例模式来设计面包类,也可以将同一个面包对象通过构造方法传递给生产者和消费者,此处使用后一种方式。 //描述生产者 class Producer implements Runnable { private Bread b; //生产者的成员:它要处理的资源 Producer(Bread b){ this.b = b; } //提供生产面包的方法 public void produce(String name){ b.name = name + b.count; b.count++; } public void run(){ while(true){ synchronized(Bread.class){ //使用Bread.class作为锁标识,使得生产者和消费者的同步代码块可以使用同一个锁 if(b.flag){ //wait()必须在同步代码块内部,不仅因为必须持有锁才能睡眠,而且对锁这个资源的判断会出现混乱 try{Bread.class.wait();}catch(InterruptedException i){} } produce("面包"); System.out.println(Thread.currentThread().getName()+"----生产者------"+b.name); try{Thread.sleep(10);}catch(InterruptedException i){} b.flag = true; //标记的切换也必须在保持同步 Bread.class.notify(); //notify()也必须同步,否则锁都已经释放了,就无法做唤醒动作 //ps:一次同步任务中,wait()和notify()应当只能其中一个执行,否则对方线程会混乱 } } } } //描述消费者 class Consumer implements Runnable { private Bread b; //消费者的成员:它要处理的资源 Consumer(Bread b){ this.b = b; } //提供消费面包的方法 public String consume(){ return b.name; } public void run(){ while(true){ synchronized(Bread.class){ if(!b.flag){ try{Bread.class.wait();}catch(InterruptedException i){} } System.out.println(Thread.currentThread().getName()+"----消费者-------------"+consume()); try{Thread.sleep(10);}catch(InterruptedException i){} b.flag = false; Bread.class.notify(); } } } } public class ProduceConsume_1{ public static void main(String[] args) { //1.创建资源对象 Bread b = new Bread(); //2.创建生产者和消费者对象,将同一个面包对象传递给生产者和消费者 Producer pro = new Producer(b); Consumer con = new Consumer(b); //3.创建线程对象 Thread pro_t = new Thread(pro); Thread con_t = new Thread(con); pro_t.start(); con_t.start(); } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/10c59baee0417f5eec6872d76daae485.html