本文目录:
1.等待、唤醒机制的原理
2.Lock和Condition
3.单生产者单消费者模式
4.使用Lock和Condition实现单生产单消费模式
5.多生产多消费模式(单面包)
6.多生产多消费模式
生产者消费者模式是多线程中最为常见的模式:生产者线程(一个或多个)生成面包放进篮子里(集合或数组),同时,消费者线程(一个或多个)从篮子里(集合或数组)取出面包消耗。虽然它们任务不同,但处理的资源是相同的,这体现的是一种线程间通信方式。
本文将先说明单生产者单消费者的情况,之后再说明多生产者多消费者模式的情况。还会分别使用wait()/nofity()/nofityAll()机制、lock()/unlock()机制实现这两种模式。
在开始介绍模式之前,先解释下wait()、notify()和notifyAll()方法的用法细节以及改进的lock()/unlock()、await()/signal()/signalAll()的用法。
1.等待、唤醒机制的原理wait()、notify()和notifyAll()分别表示让线程进入睡眠、唤醒睡眠线程以及唤醒所有睡眠的线程。但是,对象是哪个线程呢?另外,在API文档中描述这三个方法都必须在有效监视器(可理解为持有锁)的前提下使用。这三个方法和锁有什么关系呢?
以同步代码块synchronized(obj){}或同步函数为例,在它们的代码结构中可以使用wait()、notify()以及notifyAll(),因为它们都持有锁。
对于下面的两个同步代码块来说,分别使用的是锁obj1和锁obj2,其中线程1、线程2执行的是obj1对应的同步代码,线程3、线程4执行的是obj2对应的同步代码。
class MyLock implements Runnable { public int flag = 0; Object obj1 = new Object(); Object obj2 = new Object(); public void run(){ while(true){ if(flag%2=0){ synchronized(obj1){ //线程t1和t2执行此同步任务 //try{obj1.wait();}catch(InterruptedException i){} //obj1.notify() //obj1.notifyAll() } } else { synchronized(obj2){ //线程t3和t4执行此同步任务 //try{obj2.wait();}catch(InterruptedException i){} //obj2.notify() //obj2.notifyAll() } } } } } class Demo { public static void main(String[] args){ MyLock ml = new MyLock(); Thread t1 = new Thread(ml); Thread t2 = new Thread(ml); Thread t3 = new Thread(ml); Thread t4 = new Thread(ml); t1.start(); t2.start(); try{Thread.sleep(1)}catch(InterruptedException i){}; ml.flag++; t3.start(); t4.start(); } }当t1开始执行到wait()时,它将进入睡眠状态,但却不是一般的睡眠,而是在一个被obj1标识的线程池中睡眠(实际上是监视器对应线程池,只不过此时的监视器和锁是绑定在一起的)。当t2开始执行,它发现锁obj1被其他线程持有,它将进入睡眠态,这次睡眠是因为锁资源等待而非wait()进入的睡眠。因为t2已经判断过它要申请的是obj1锁,因此它也会进入obj1这个线程池睡眠,而不是普通的睡眠。同理t3和t4,这两个线程会进入obj2线程池睡眠。
当某个线程执行到notify()时,这个notify()将 随机 唤醒它 所属锁对应线程池 中的 任意一个 线程。例如,obj1.notify()将唤醒obj1线程池中任意一个睡眠的线程(当然,如果没有睡眠线程则什么也不做)。同理notifyAll()则是唤醒所属锁对应线程池中所有睡眠的线程。
必须要搞清楚的是"对应锁",因为在调用wait()、notify()和notifyAll()时都必须明确指定锁。例如,obj1.wait()。如果省略了所属锁,则表示的是this这个对象,也就是说,只有在非静态的同步函数中才能省略这三个方法的前缀。
简而言之,当使用了同步,就使用了锁,线程也就有了归属,它的所有依据都由所属锁来决定。例如,线程同步时,判断锁是否空闲以决定是否执行后面的代码,亦决定是否去特定的线程池中睡眠,当唤醒时也只会唤醒所属锁对应线程池中的线程。
这几个方法在应用上,一般在一次任务中,wait()和notify()/notifyAll()是成对出现且择一执行的。换句话说,就是这一轮原子性同步执行过程中,要么执行wait()进入睡眠,要么执行notify()唤醒线程池中的睡眠线程。要如何实现择一执行,可以考虑使用标记的方式来作为判断依据。参考后文的例子。
2.Lock和Condition