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


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

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

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

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线程启动线程 t1
main线程执行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线程启动线程 t1
main线程执行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维护的等待队列中之后,来进行竞争获取锁。只有在这个队列中才有资格(并不一定会)获取锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值