关于java多线程浅析五: Condition条件

来源:互联网 发布:r语言数据分析实例 编辑:程序博客网 时间:2024/06/02 04:08

java.util.concurrent 包在java语言中可以说是比较难啃的一块,但理解好这个包下的知识,对学习java来说,不可谓是一种大的提升,我也尝试着用自己不聪明的脑袋努力的慢慢啃下点东西来。其实 java.util.concurrent 包中,最核心的就是AQS( AbstractQueuedSynchronizer) 这个抽象类,可以说是整个JUC包的基石,但今天先不说AQS,我先从比较容易理解的 Condition 条件讲起。

什么是Condition条件

Condition 是定义在 java.util.concurrent.locks 包下的一个接口,这个接口主要的功能就是实现了与Object类中的wait(),notify()方法相同的语义,但是功能上更强大。Condition 接口中定义的方式其实很少,列举下来:

    //使当前线程接到signal信号之前,或者被中断之前一直处于等待状态    void await() throws InterruptedException;    //使当前线程接到signal信号之前一直处于等待状态    void awaitUninterruptibly();     //使当前线程接到signal信号之前,或到达指定等待的时间之前,或者被中断之前一直处于等待状态    boolean await(long time, TimeUnit unit) throws InterruptedException;     //使当前线程接到signal信号之前,或到达指定的最后期限时间之前,或者被中断之前一直处于等待状态    boolean awaitUntil(Date deadline) throws InterruptedException;    //使当前线程接到signal信号之前,或到达指定等待的时间之前,或者被中断之前一直处于等待状态    long awaitNanos(long nanosTimeout) throws InterruptedException;    //向一个线程发送唤醒信号    void signal();     //向所有线程发送唤醒信号    void signalAll(); 

从定义的方法中也可以看出这个类的功能,无非就两种:等待方法、唤醒等待方法。

Condition 的使用

下面用一个之前用的小demo展示一下 Condition 的使用方法:
先使用Object中的wait,notify 方法:

public class TestCondition {    public static void main(String[] args) {        try {            ThreadTest t1 = new ThreadTest("t1");            synchronized (t1) {                System.out.println(Thread.currentThread().getName()+"线程启动线程 t1");                t1.start();                System.out.println(Thread.currentThread().getName()+"线程执行wait方法,等待被唤醒");                t1.wait();                System.out.println(Thread.currentThread().getName()+"线程继续执行");            }        } catch (InterruptedException e)             e.printStackTrace();        }    }}class ThreadTest extends Thread{    public ThreadTest(String name){        super(name);    }    public void run(){        synchronized (this) {            System.out.println(Thread.currentThread().getName()+"线程执行 notify 方法");            notify();           }    }}

运行结果:

main线程启动线程 t1main线程执行wait方法,等待被唤醒t1线程执行 notify 方法main线程继续执行

这里就不再详述关于 synchronized 关键字的使用,举这个示例是为了引出Condition的使用。
稍微改一下上面的程序:

public class TestCondition {    private static ReentrantLock lock = new ReentrantLock();    private static Condition condition = lock.newCondition();    public static void main(String[] args) {        try {            ThreadTest t1 = new ThreadTest("t1");                lock.lock();                System.out.println(Thread.currentThread().getName()+"线程启动线程 t1");                t1.start();                System.out.println(Thread.currentThread().getName()+"线程执行condition.await()方法,等待被唤醒");                condition.await();                System.out.println(Thread.currentThread().getName()+"线程继续执行");        } catch (InterruptedException e) {            e.printStackTrace();        }finally{            lock.unlock();        }    }    static class ThreadTest extends Thread{        public ThreadTest(String name){            super(name);        }        public void run(){            try{                lock.lock();                System.out.println(Thread.currentThread().getName()+"线程执行condition.signal()方法");                condition.signal();            }finally{                lock.unlock();            }        }    }}

运行结果:

main线程启动线程 t1main线程执行condition.await()方法,等待被唤醒t1线程执行condition.signal()方法main线程继续执行

根据上面的实例可以看出,Condition工具类是配合Lock类一起使用的,当然Condition的作用远不止上面代码这样简单,其实它最主要的作用是可以在同一把锁上,针对不同的业务使用不用的Condition。比如在前面的文章提到的生产消费问题,我们完全可以使用两个Condition,一个针对于生产,一个针对于消费,当产品为0时,我们可以让消费的Condition执行await方法,当产品不为0时,可以让消费的Condition执行signal方法。生产者也是类似,具体代码这里就不再详述了,可以尝试自己实现一下。

Condition 的实现类:ConditionObject

Condition 是一个接口,那它到底是怎么工作的呢?我们来看一下ReentrantLock 类是怎么样使用Condition 的。

Condition condition = lock.newCondition();//这是生成Condition 的方法

追踪一下newCondition( )这个方法,我们就可以看到一个Condition 的具体实现:

 public Condition newCondition() {        return sync.newCondition(); //调用sync的newCondition    }//sync的方法,返回一个new ConditionObject()final ConditionObject newCondition() {        return new ConditionObject();    }

这个sync是什么呢?它是定义在ReentrantLock 中的内部类,它继承了上面提到的AQS这个抽象类,从某种角度来说ReentrantLock 只是提供了一个可以操作AQS这个核心类的入口,代理了一些重要的方法,那为什么不让ReentrantLock 直接继承AQS,而是选择用一个内部类来实现呢,可能是出于一些安全性方面的考虑。ConditionObject是定义在AQS中的。

这里写图片描述

这里写图片描述

Condition 与 AQS 到底是怎么工作的?

探究这个问题,就需要来看一下ConditionObject的源码了。
代表性的,我们看一下await方法和signal方法的源码(基于jdk1.8.0_112):
await方法:

 public final void await() throws InterruptedException {            if (Thread.interrupted())                throw new InterruptedException(); //判断线程的中断状态,如果中断则抛出异常            //将当前线程包装成一个条件等待节点添加到ConditionObject维护的一个队列中            Node node = addConditionWaiter();            //释放当前线程占有的锁            int savedState = fullyRelease(node);            int interruptMode = 0;            //这个while 循环就是在当前线程释放锁后,一直观察持有自己线程的节点有没有被加载到            //AQS维护的等待队列中(加入到这个队列中才有获取锁的资格),什么时候会加入到这个队列中呢?             //当然是执行了唤醒这个线程对应的singal方法的时候啦            while (!isOnSyncQueue(node)) {                LockSupport.park(this);                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)                    break;            }            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)                interruptMode = REINTERRUPT;            if (node.nextWaiter != null) // clean up if cancelled                unlinkCancelledWaiters();            if (interruptMode != 0)                reportInterruptAfterWait(interruptMode);        }

在看一下signal方法:

 public final void signal() {            if (!isHeldExclusively())                throw new IllegalMonitorStateException();            Node first = firstWaiter;//ConditionObject维护队列中的头节点,进行唤醒操作            if (first != null)                doSignal(first);        }

从上面的叙述不难看出,AQS 和 ConditionObject各自维护了一个队列,用来存放包装了线程的Node节点。联系上面的代码示例和源码可以总结一下AQS和ConditionObject中的队列是怎么被维护的:

(1) 示例中存在两个线程:main线程 t1线程

(2) 当main线程调用lock.locak()方法时,main获取到锁,继续执行, 当执行到t1.start()方法时,t1也想获取lock,但是此时该锁已经被main线程占用,所以t1线程进入 AQS维护的等待队列中,等待机会获取cpu的占用权。

(3) 当main线程执行了condition.await()方法时,main线程就在释放占用锁的同时加入到了ConditionObject维护的等待队列中,在这个队列中的线程,如果signal状态不发生改变,是永远没有机会获取到cpu的占有权的。

(4) 好,这个时候main已经进入了ConditionObject维护的等待队列中,那么AQS维护的等待队列中的t1线程就可以获取cpu的占有权了,可以继续执行。

(5) 当t1线程执行到 condition.signal() 方法时,就会唤醒ConditionObject维护的等待队列中的头节点,也就是main线程。但是注意,这里唤醒的意思是将main线程节点放到AQS维护的等待队列中,然后听从AQS的调度,并不是马上就能获取cpu的占有权。

(6) 然后t1线程执行结束,unlock释放占用的锁,在AQS维护的等待队列中的main就能继续执行下去了。

总之:

  • ConditionObject维护的等待队列的功能是存放那些执行了await方法的线程,等待收到signal信息好可以进入AQS维护的等待队列中。在这个队列中是不会获取到锁的。
  • AQS维护的等待队列存放那些就绪状态的线程,只等待目前占有锁的家伙执行完或者进入了ConditionObject维护的等待队列中之后,来进行竞争获取锁。只有在这个队列中才有资格(并不一定会)获取锁。
原创粉丝点击