Java同步技术(四)

3.queue():返回等待条件的线程的数量

根据上面的定义我们可以看出,我们可以把条件变量当成是一个在临界区内使用的特殊的事件(Event),因为它们要实现的功能是一样的,只是多了一个释放和恢复管程控制权的操作。另外,我们可以看到条件变量的加入以及同一管程可以有多个条件变量的特性,使我们得以按照业务需要将对同一资源的等待进程分别在不同的条件变量上排队,使得我们可以对资源的访问进程进行分组控制。

下面,我们试着用管程和条件变量来解决上述问题,代码清单如下: monitor conditionTest{private: double balance, account; // 共享变量 int flag=FALSE; // 条件 condition c; // 条件变量public: programForP1(); programForP2();}programForP1(){ // <临界区A> … while(!flag) c.wait(); // 1. wait,等待flag=true ———————————————————-> balance=balance+account; // 6. 继续执行}programForP2(){ // <临界区B> … balance=balance-account; // 2.P2代码 flag=true; // 3.设置 flag c.signal(); // 4.唤醒P1 … // 5.P2代码}<——————–

下图显示了显示了现实生活中共享资源争用的例子。如图所示,所有的车(线程)都要通过十字路(临界区),然而两条路上的车队(线程队列)都要等待信号灯(条件变量的信号),如果现在有1线的信号灯,那么1号线的车就可以通行,而2号线中的车还必须等待.由此可见每个信号灯(条件变量)只对一条线路上的车辆起作用(只能唤醒等待此条件变量的线程).

2.3.2、条件变量的Java实现

1 Java中的Object类

通过前面对synchronized关键字的讨论,我们可以得出一个结论就是“所有被synchronized关键同步的对象(Object)都可以看做是一个管程(Monitor)”。而Java语言也在所有Java类的基类Objcet类中为我们准备了几个用于线程间调度的方法。wait(),notify(),notifyAll()。这几个方法必须用在synchronized标识所在对象的临界区内使用, 也就是说必须在当对象作为管程对象时才能使用,否则就会抛出“无效的管程状态异常” java.lang.IllegalMonitorStateException。它们的作用分别是:

wait(); 释放同于同步而被独占的管程对象的控制权,并挂起发启者线程。notify(): 唤醒一个由于调用wait()而处于等待中的线程。如果没有线程就忽略此操作。notifyAll(): 唤醒所有由于调用wait()处于等待中的线程。如果没有线程就忽略此操作。当然最后只有一个会获得管程的控制权。

大家注意到了这些方法所提供的功能以及使用的条件都很象条件变量。实际上对于POSIX系统的条件变量方法wait(), timed_wait(), signal(), broadcast()提供的功能和就直接对应Java中wait(), wait(long), notify(), notifyAll()方法所提供的功能。它们是的功能是一致的, 只是它只能完成1个条件变量的任务,如果要实现在一个管程中两个以上条件变量协调工作的功能就不行了,也就是说不论什么条件下wait()的线程都处于一个等待队列中,而notify()时也无法有选择的唤醒符合某一条件的一组等待线程.所以我们可以认为wait(),notify()等方法都是对sycnhonized同步锁内的一个匿名的条件变量的调用。

2. 锁接口(Lock)

前面我们讨论过,虽然synchronized关键字可以满足我们大部分的同步需求.但是还是有一些特殊的情况, 比如在本节我们要讨论的”条件变量”就需要在两个不同的方法中去完成加锁和解锁操作,这时sychronized就不适用了.因此我们还是有必要重新抽象出一个Lock接口类去完成与synchronized相同的功能(进一步研究我们会发现临界区的锁有很多种,如Mutex互斥锁、读写锁等,因此这里我们抽象的是Lock接口而不是一个具体的类),这个锁应当具有如下所示的几个接口方法:

/*** 锁(临界区)接口* @author iangao*/public interface Lock { /** * 加锁(进入临界区) * @param mills 等待mills时间 */ boolean lock(long mills) throws InterruptedException; /** * 加锁(进入临界区) */ boolean lock() throws InterruptedException; /** * 解锁(离开临界区) * @throws InterruptedException */ void unlock(); /** * 放弃锁(临界区)控制权 * @return 反回当前锁的层数,用于恢复时使用 */ long relinquish(); /** * 恢复锁(临界区)控制 * @param holdCount 恢复放弃之前的加锁层数 */ void restore(long holdCount) throws InterruptedException;}

2 条件变量的Java实现

通过前面的分析我们可以看出Condition实际上就是管程状态下的Event应用,在wait操作中多出了对管程控制权的释放和恢复功能。因此下面的实现中我们直接通过继承Event类来实现Condition. 代码清单如下:

/*** 条件变量 — 管程内的事件(Event)* @author iangao*/public class Condition extends Event{ private Lock lock; // 条件所在临界区锁 /** * 与一个锁(管程)相关联 * @param mutex 互斥锁 */ public Condition(Lock lock){ this.lock=lock; } /** * 等待条件满足信号 * @param millis 等待时间,0表示死等 */ public void await(long millis) throws InterruptedException{ long holdCount=lock.relinquish(); // 放弃临界区,记录锁层数 boolean success=super.await(millis); // 等待条件信号 if(!success) return false; // 重获控制权失败 lock.restore(holdCount); // 恢复临界区状态,直接加holdCount层 return success; }}

3. 扩展Mutex类

由于Mutex类本身就是实现锁功能的,因此本节对其进行扩展,使其包含Lock接口.这样Mutex就可以做为Lock使用了.扩展后的Mutex类如下

/*** 互斥信号量* @author iangao*/public class Mutex implements Lock{ …… public boolean lock(long mills) throws InterruptedException{ return p(mills,1); } public boolean lock() throws InterruptedException{ return lock(0); } public void unlock(){ v(false); } public long relinquish() { return v(true); } public void restore(long holdCount) throws InterruptedException { p(0,holdCount); }}

4 测试条件变量的应用

下面我们演示一下使用这套机制实现的条件变量是如何工作的.如下所示,我们要创建3个线程,并做如下测试:线程1等待条件A,线程2等待条件B,线程3唤醒等待条件B的线程(即线程2),接下来线程2可以执行,而线程1将继续等待。

public class ConditionTest { public static void main(String[] args){ new ThreadsTest(){ private Lock lock=new Mutex(); private Condition conditionA=new Condition(lock); private Condition conditionB=new Condition(lock); /** * 线程1: 等conditionA信号 */ public void runInThread1() throws InterruptedException{ try { lock.p(); System.out.println(“[T1]: 等待conditionA …”); conditionA.await(); System.out.println(“[T1]: 获得conditionA信号”); } finally{ lock.v(); } } /** * 线程2: 等conditionB信号 */ public void runInThread2() throws InterruptedException{ try{ lock.p(); output(“[T2]: 等待conditionB …”); conditionB.await(); output(“[T2]: 获得conditionB信号”); } finally{ lock.v(); } } /** * 线程3: 发出ConditionB信号 */ public void runInThread3() throws InterruptedException{ try { lock.p(); output(“[T3]: 执行一些操作(2秒)”); Thread.sleep(2000); output(“[T3]: 发出conditionB信号(2秒)”); conditionB.signal(); Thread.sleep(2000); output(“[T3]: 发出conditionB信号后的操作”); }finally{ lock.v(); } } }.execute(3); }}

测试结果:[T1]: 等待conditionA …[T2]: 等待conditionB …[T3]: 执行一些操作(2秒)[T3]: 发出conditionB信号(2秒)[T3]: 发出conditionB信号后的操作[T2]: 获得conditionB信号 // 等T3释放管程后才会响应,因为condition要重获管程控制权

在上面的例子中,我们实现了Thread1为在ConditionA队列上等待,Thread2在ConditionB队列上等待,面Thread3则是用于唤醒ConditionB上等待对队列的,而mutex在此处起到管程的作用,从而保证Thread1,Thread2,Thread3三个线程在执行时间上是互斥的。

4. J2SE 1.5中的条件变量

在1.5版中提供了一个java.util.concurrent.locks.Condition的接口.该接口通过await()方法实现等待信号功能,通过signal()和signalAll()方法实现发送唤醒信号功能。与wait()、notify()是与sychronized关键字绑在一起使用类似,Condition接口也是与前面提到的java.util.concurrent.locks.Lock接口类绑在一起的。这节我们关注的是Lock中的newCondition()方法,因为它可以创建出一个与Lock对象相关的Condition对象, 而且此Condition对象只能在使用Lock接口中lock()和unlock()方法划定出的临界区内使用。

这个社会是存在不公平的,不要抱怨,因为没有用!人总是在反省中进步的!

Java同步技术(四)

相关文章:

你感兴趣的文章:

标签云: