一.Condition 概述以及API
1.概述
在java1.5之前,线程之间的通信主要通过notify和wait。而Condition支持多路等待,就是定义多个Condition,每个Condition控制一个支路,典型问题生产者和消费者问题现在可以通过这个接口来进行优化。
2.API
1.await(),当前线程等待,同时释放当前锁,可以用signal()时或者signalAll()方法或者中断跳出等待,线程会重新获得锁并继续执行,和之前的Object的wait很像(await这个方法必须在lock和unlock之间调用)。
2.awaitUninterruptibly(),与await()方法基本相同,但这个方法不会在等待过程中响应中断,也就是中断不会跳出等待,继续睡。
3.singal(),用于唤醒一个在等待中的线程,和notify类型
二.使用实例-生产者消费者模型
1.仓库类Store的设计
这个仓库类包含生产方法和消费方法,在里面设置锁,以及条件来进行线程的通信。而生产者和消费者实例会持有这个仓库类的实例,他们的生产消费方法是通过调用这个仓库的相应的方法来实现的,来看看仓库类把。
<1>仓库具体设计
//设置锁以及条件,并用构造函数初始化 private final static int MAX_NUM = 100; private Lock lock; private Condition p; private Condition c; public Store() { lock =new ReentrantLock(); c = lock.newCondition(); p = lock.newCondition(); }
//用一个链表作为仓库,用LinkedList是因为业务主要是增删,所以用它比较快 private LinkedListlist = new LinkedList (); // get/set方法 public LinkedList getList() { return list; } public void setList(LinkedList list) { this.list = list; } public int getMAX_SIZE() { return MAX_NUM; }
<2>生产方法设计
//具体的生产方法public void produce(int num) { lock.lock();//条件要放在lock.lock和lock.unlock之间 try{ // 仓库容量不足 while (list.size() + num > MAX_NUM) { System.out.println("【要生产的产品数量】:" + num + " 【库存量】:" + list.size() + "暂时不能执行生产任务!"); try { p.await();//当库存量不足时,不能生产,所以设置生产者阻塞,用条件p。 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 仓库容量足够,生产num个产品 for (int i = 1; i <= num; ++i) { list.add(new Random().nextInt(100)); } System.out.println("【已经生产产品数】:" + num + " 【现仓储量为】:" + list.size()); c.signalAll();//生产完之后唤醒消费者条件,这个时候c.await的线程会唤醒 TimeUnit.SECONDS.sleep(1); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } }
<3>消费方法设计
//消费方法 public void Consume(int num) { lock.lock(); // 仓库容量不足 try{ while (list.size() < num) { System.out.println("【要消费的产品数量】:" + num + " 【库存量】:" + list.size() + "/t暂时不能执行消费任务!"); try { c.await();//等到生产者那边c.singalAll时在这里阻塞的线程会唤醒,继续往下执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 仓库容量足够,消费num个产品 for (int i = 1; i <= num; ++i) { list.remove(); } System.out.println("【已经消费产品数】:" + num + " 【现仓储量为】:" + list.size()); p.signalAll();//消费者消费完之后,会唤醒生产者继续生产. TimeUnit.SECONDS.sleep(1); }catch(Exception e){ e.printStackTrace(); }finally{ lock.unlock(); } }
2.生产者消费者的设计
这部分很简单,都是实现Runnable接口,通过构造函数初始化Store的实例,run方法调用Store的相应的生产消费方法,并传入一个生产消费的数量,就基本稳了。
3.测试类
实例化Store,传给生产者消费者,启动线程把任务丢进去,跑起来就行啦。
三.Condition接口的原理
1.等待队列
Condition持有多个FIFO的等待队列,当前线程调用await方法时,那么这当前线程就会释放锁,进入这个等待队列的尾部进入等待状态。Condition拥有这个队列的首节点(firstWaiter)和尾节点(lastWaiter).其实一个Lock(同步器)有一个同步队列(准备去获得锁)以及多个等待队列(本来已经有锁,但是中途用了await方法),还记得之前讨论的监视器么,和这个很像,监视器只有一个同步队列和一个等待队列。其实工作原理一样的,没有获取锁的线程在同步队列等待锁,获得锁后进入特殊的房间(资源),调用await之类的等待方法就把同步队列的头结点移到等待队列的尾节点。当等待队列里面的节点被唤醒后,进入同步队列拿锁进入同步状态。
2.通知
调用Condition的singal方法,它将会先将在等待队列中等待时间最长的节点(首节点)移到同步队列(同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列),然后唤醒它。进入锁的竞争状态(acquireQueued()方法)。不过注意的是当前线程或得锁才会调用这个方法使等待队列里面的首节点进入同步队列。比如说:同步队列里面有p1,p2,p3三个消费者线程.等待队列里面有c1,c2,c3三个消费者线程。p1为头结点,p1获得锁去生产,然后调用在singal方法时,p1必定是要先有锁的,然后c1进入同步队列,p2获得锁进行生产,然后就到c1进行消费。