多线程——Condition的介绍以及用法
之前总结了wait和notify的用法,今天总结一下condition的用法。
Condition介绍:
关键字synchronize可以与wait()和nitify()方法相结合实现实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助condition对象。condition类是在JDK5中出现的技术,使用他有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里可以创建多个condition实例,线程对象可以注册在指定的condition中从而选择性的进行线程通知,在调度线程上更加灵活。
而在使用notify()/notifuAll()方法进行通知时,被调度的线程却是由JVM随机选择的。但使用ReentrantLock结合condition类是可以实现上面讲的“选择性通知”,这个功能是非常重要的,而且在condition类中默认提供的。
而synchronize就相当于整个Lock对象中只有一个单一的condition对象,所有的线程都注册在它一个对象上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。
-
Condition是个接口,基本的方法就是await()和signal()方法
-
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
-
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
-
Conditon中的await()对应Object的wait();
-
Condition中的signal()对应Object的notify();
-
Condition中的signalAll()对应Object的notifyAll()。
等待/通知模式:
demo如下:
运行结果:
从运行结果来看,实现了一个最简单的等待/通知模式。
实现通知部分线程:
两个线程类:
mian函数如下:
运行结果如下:
从运行结果看出来,两个线程最终只唤醒了线程A,线程B没有被唤醒,实现了使用condition通知部分线程。
还可以使用condition实现生产者/消费者模式,有兴趣的可以自己去查查看。
Condition实现分析
Condition 是一个接口,由ConditionObject 类实现,并且它还是AQS(AbstractQueuedSynchronizer)的内部类。
从源码中可以看到,每个Condition对象都包含有一个FIFO队列,等待队列和同步队列一样,使用的都是同步器 AQS 中的节点类 Node,同样拥有首节点和尾节点。
await()方法:
调用 Condition 的 await() 方法会使线程进入等待队列,并释放锁,线程状态变为等待状态。
分析上述方法的大概过程:
- 先将当前线程包装创建为节点。
- 释放当前线程占有的锁,
- while循环判断当前节点是否存在队列中,
3.1 如果没有则阻塞,继续while判断
3.2如果存在,则退出while循环,执行下面的代码 - 此时说明节点已经在同步队列中,调用acquireQueude方法,重新开始竞争锁,
- 得到锁后返回,退出该方法。
signal()方法:
调用 Condition 的 signal() 方法,可以唤醒等待队列的首节点,唤醒之前会将该节点移动到同步队列中。
分析上述方法大概过程:
1.先判断当前线程是否有锁,
2.然后对首节点调用doSignal()方法。
1.修改首节点,直到获取到为取消的节点,
2.调用transferForSignal方法。
调用enq方法,节点加入队列,满足条件后调用LockSupport唤醒线程
(我的天,快看不懂了。)
signalAll()方法:
将等待队列中的全部节点移动到同步队列中,调用signalAll()方法,循环来唤醒每一个等待队列中的节点,直到 first 为 null 时,停止循环,并唤醒每个节点的线程。
总结:
整体就是调用condition的await()方法后,会将当前线程加入到等待队列中,然后释放锁,然后循环判断节点是否在同步队列中,再获取锁,否则一直阻塞。
调用signal()方法后,先判断当前线程是否有锁,然后调用doSignal()方法,并唤醒线程,被唤醒的线程,再调用acquireQueude()方法,重新开始竞争锁,得到锁后返回,退出该方法。