Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。Condition是个接口,基本的方法就是await()和signal()方法。
文章目录
Condition简介
任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的, 在java Lock体系下依然会有同样的方法实现等待/通知机制。从整体上来看Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。两者除了在使用方式上不同外,在功能特性上还是有很多的不同:
Condition方法
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()
Condition接口中的方法如下所示
Condition实例
一道面试题如下:
要求用三个线程完成如下操作,
线程A打印5次A
线程B打印10次B
线程C打印15次
线程A打印5次A
线程B打印10次B
…
如此循环打印
使用3个Condition进行线程调度
package cn.wideth.util;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class PrintData {
private int number = 1;
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5() throws InterruptedException {
lock.lock();
try {
while (number != 1) { // 多线程的判断使用while进行判断
c1.await(); // 如果number不为1(说明不是它工作)让当前线程进行等待
}
// 打印5次
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + String.valueOf(i));
}
number = 2;// 修改number值为2
c2.signal();// 通知线程2让他开始工作
} finally {
lock.unlock();
}
}
public void print10() throws InterruptedException {
lock.lock();
try {
while (number != 2) { // 如果number不为2则进行等待,否则执行后面的打印10次逻辑
c2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " -->" + String.valueOf(i));
}
number = 3; // 修改number的值为3
c3.signal();// 通知3开始工作
} finally {
lock.unlock();
}
}
public void print15() throws InterruptedException {
lock.lock();
try {
while (number != 3) { // 如果number不为3则进行等待
c3.await();
}
//打印15次
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + String.valueOf(i));
}
number = 1;// 修改为1
c1.signal();// 通知1工作
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
PrintData printData = new PrintData();
new Thread(() -> {
for (int i = 0;i<10 ; i++) {// 只打印10次防止太多次了,若要一直循环则去掉i的条件判断
try {
printData.print5();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i<10 ; i++) {
try {
printData.print10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0;i<10 ; i++) {
try {
printData.print15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
}
}
运行结果:
使用一个Condition完成线程调度
package cn.wideth.util;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class PrintData {
private int number = 1;
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
public void print5() throws InterruptedException {
lock.lock();
try {
while (number != 1) { // 多线程的判断使用while进行判断
c1.await(); // 如果number不为1(说明不是它工作)让当前线程进行等待
}
// 打印5次
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + String.valueOf(i));
}
number = 2;// 修改number值为2
c1.signalAll();// 通知线程2让他开始工作
} finally {
lock.unlock();
}
}
public void print10() throws InterruptedException {
lock.lock();
try {
while (number != 2) { // 如果number不为2则进行等待,否则执行后面的打印10次逻辑
c1.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + String.valueOf(i));
}
number = 3; // 修改number的值为3
c1.signalAll();// 通知3开始工作
} finally {
lock.unlock();
}
}
public void print15() throws InterruptedException {
lock.lock();
try {
while (number != 3) { // 如果number不为3则进行等待
c1.await();
}
//打印15次
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + String.valueOf(i));
}
number = 1;// 修改为1
c1.signalAll();// 通知1工作
} finally {
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
PrintData printData = new PrintData();
new Thread(() -> {
for (int i = 0;i<10 ; i++) { // 只打印10次防止太多次了,若要一直循环则去掉i的条件判断
try {
printData.print5();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i<10 ; i++) {
try {
printData.print10();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0;i<10 ; i++) {
try {
printData.print15();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
}
}
源码分析
等待队列
要想能够深入的掌握condition还是应该知道它的实现原理,现在我们一起来看看condiiton的源码。创建一个condition对象是通过lock.newCondition(),而这个方法实际上是会new出一个ConditionObject对象,该类是AQS的一个内部类。前面我们说过,condition是要和lock配合使用的也就是condition和Lock是绑定在一起的,而lock的实现原理又依赖于AQS,自然而然ConditionObject作为AQS的一个内部类无可厚非。我们知道在锁机制的实现上,AQS内部维护了一个同步队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列,同样的,condition内部也是使用同样的方式,内部维护了一个 等待队列,所有调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。另外注意到ConditionObject中有两个成员变量:
/**
* Condition implementation for a {@link
* AbstractQueuedSynchronizer} serving as the basis of a {@link
* Lock} implementation.
*
* <p>Method documentation for this class describes mechanics,
* not behavioral specifications from the point of view of Lock
* and Condition users. Exported versions of this class will in
* general need to be accompanied by documentation describing
* condition semantics that rely on those of the associated
* {@code AbstractQueuedSynchronizer}.
*
* <p>This class is Serializable, but all fields are transient,
* so deserialized conditions have no waiters.
*/
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
/**
* Creates a new {@code ConditionObject} instance.
*/
public ConditionObject() { }
这样我们就可以看出来ConditionObject通过持有等待队列的头尾指针来管理等待队列。主要注意的是Node类复用了在AQS中的Node类。
await实现原理
当调用condition.await()方法后会使得当前获取lock的线程进入到等待队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的lock。接下来,我们还是从源码的角度去看,只有熟悉了源码的逻辑我们的理解才是最深的。await()方法源码为:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 1. 将当前线程包装成Node,尾插入到等待队列中
Node node = addConditionWaiter();
// 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 3. 当前线程进入到等待状态
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 4. 自旋等待获取到同步状态(即获取到lock)
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 5. 处理被中断的情况
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
signal实现原理
调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得lock。按照等待队列是先进先出(FIFO)的,所以等待队列的头节点必然会是等待时间最长的节点,也就是每次调用condition的signal方法是将头节点移动到同步队列中。我们来通过看源码的方式来看这样的猜想是不是对的,signal方法源码为:
public final void signal() {
//1. 先检测当前线程是否已经获取lock
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//2. 获取等待队列中第一个节点,之后的操作都是针对这个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
本文小结
Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到 Condition对象关联的锁。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。获取一个Condition必须通过Lock的newCondition()方法。