关于java线程浅谈五: Condition条件

来源:互联网 发布:macbook藏文件软件 编辑:程序博客网 时间:2024/05/18 11:48

【CSDN 技术主题月】物联网全栈开发     【评论送书】每周荐书:MySQL、Kafka、微信小程序     【直播】Android 老司机带你开黑

关于java线程浅谈五: Condition条件

标签: java线程并发condition
223人阅读 评论(0)收藏举报
本文章已收录于:
分类:
作者同类文章X

    目录(?)[+]

    1. 什么是Condition条件
    2. Condition 的使用
    3. Condition 的实现类ConditionObject
    4. Condition 与 AQS 到底是怎么工作的

    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(); 
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

    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();           }    }}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    运行结果:

    main线程启动线程 t1main线程执行wait方法,等待被唤醒t1线程执行 notify 方法main线程继续执行
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    这里就不再详述关于 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();            }        }    }}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    运行结果:

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

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

    Condition 的实现类:ConditionObject

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

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

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

     public Condition newCondition() {        return sync.newCondition(); //调用sync的newCondition    }//sync的方法,返回一个new ConditionObject()final ConditionObject newCondition() {        return new ConditionObject();    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个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);        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在看一下signal方法:

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

    从上面的叙述不难看出,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维护的等待队列中之后,来进行竞争获取锁。只有在这个队列中才有资格(并不一定会)获取锁。
    原创粉丝点击