介绍
任意Java对象都拥有一组监视器方法定义在java.lang.Object方法上,主要包括:
//等待
public final void wait() throws InterruptedException
//超时限制等待
public final native void wait(long timeout) throws InterruptedException
//超时限制等待
public final void wait(long timeout, int nanos) throws InterruptedException
//通知某个线程
public final native void notify()
//通知所有等待线程
public final native void notifyAll()
复制代码
上面这些方法与synchronized同步关键字配合,实现等待/通知模式。类似Object的监视器方法,Condition接口与Lock配合也可以实现等待/通知模式。但两者在使用方式和功能特性上是有所差别的。
我们来看下Object的监视方法和Condition接口的对比:
对比项 | Object Monitor Methods | Condition |
---|---|---|
前置条件 | 获取对象的锁 | 调用Lock.lock()获取锁 调用Lock.newCondition获取Condition对象 |
调用方式 | 直接调用,如:object.wait() | 直接调用,如:condition.await() |
等待队列个数 | 一个 | 多个 |
当前线程释放锁并进入等待队列 | 支持 | 支持 |
当前线程释放锁并进入等待队列,在等待状态中不响应中断 | 不支持 | 支持 |
当前线程释放锁并进入超时等待状态 | 支持 | 支持 |
当前线程释放锁并进入到等待状态到将来的某个时间 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的全部线程 | 支持 | 支持 |
Condition接口与Lock重入锁是怎么配合的那?通过lock接口(重入锁实现了这个接口)的Condition newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。利用Condition对象,便可以让线程合适时间等待或在某一时刻获得通知从而继续执行。Condition是依赖Lock对象的,需要提前获取到Condition对象关联的锁。
Condition定义的基本方法
//使当前线程等待,同时释放当前锁
//跳出等待的情况:
//1.其它线程调用该Condition的sign()或者signAll()方法时,线程会重新获得锁并继续执行
//2.其它线程中断当前线程(调用interrupt()方法)
void await() throws InterruptedException
//与await()基本相似,但它不会在等待过程中响应中断
void awaitUninterruptibly()
//当前线程进入等待状态,直到被通知、中断或者超时。返回值(nanosTimeout-实际耗时)表示剩余时间。如果返回0或者负数表示已经超时
long awaitNanos(long nanosTimeout) throws InterruptedException
//当前线程进入等待状态, 直到被通知、中断或者超时。参数设置了超时时间,如果执行时间超过该时间限制还未收到通知则直接返回false
boolean await(long time, TimeUnit unit) throws InterruptedException
//前线程进入等待状态, 直到被通知、中断或某个时间。没到指定时间被通知返回true,否则,表示到了指定时间,方法返回false
boolean awaitUntil(Date deadline) throws InterruptedException
//唤醒一个在Condition等待的线程
void signal()
//唤醒所有等待在Condition上的线程
void signalAll()
复制代码
在JDK内部,重入锁和Condition对象被广泛应用,例如ArrayBlockQueue的put和take方法,有兴趣的话可以翻阅查看。
Condition的实现分析
我们先来看下newCondition的实现方法:
final ConditionObject newCondition() {
return new ConditionObject();
}
复制代码
通过newCondition我们会获得一个ConditionObject对象,ConditionObject为同步器AQS的内部类。因为Condition操作需要获取相关联的锁,所以作为同步器的内部类也较为合理。每个Condition对象包含着一个等待队列,那么该队列就是Condition对象实现等待/通知功能的关键。
1.等待队列
等待队列是一个FIFO的队列,节点为Condition对象上等待的线程。重入锁维护的同步队列和Condition上的等待队列节点类型都是同步器的静态内部类AbstractQueuedSynchronizer.Node。
ConditionObject对象有下面两个属性:
/** First node of condition queue. */
//等待队列的首节点
private transient Node firstWaiter;
/** Last node of condition queue. */
//等待队列的尾节点
private transient Node lastWaiter;
复制代码
可见Condition拥有首节点(firstWaiter)和尾节点(lastWaiter)来管理等待队列,等待队列的基本结构如下所示:
如上图所示,Condition拥有首尾节点引用的单向队列,新增节点只需将原来的尾节点nextWaiter指向它,并更新尾节点即可。
在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列(lock可创建多个condiction对象),对应关系如下图所示:
如图所示,同步器维护的同步队列是一个双向队列。Condition的实现是同步器的内部类,因此每个Condition实例都能够访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用。
2.等待
我们对Condition的await()方法源码来分析下:
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException.
* <li> Save lock state returned by {@link #getState}.
* <li> Invoke {@link #release} with saved state as argument,
* throwing IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted.
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw InterruptedException.
* </ol>
*/
public final void await() throws InterruptedException {
//如果线程被中断则抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程构造一个新的节点加入等待队列
Node node = addConditionWaiter();
//释放同步状态,也就是释放锁,唤醒线程队列中的后继节点
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断节点是否在同步队列(上面已经释放锁加入到等待队列)
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);
}
复制代码
调用await()方法的前提是该线程获取了锁。我们看到添加到等待队列,调用了addConditionWaiter()方法,该方法会把当前线程构造一个新的节点加入到等待队列尾部。
**
* Adds a new waiter to wait queue.
* @return its new wait node
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//将当前节点包装成Node,waitStatus状态为Node.CONDITION(表明线程在该Condition等待)
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
//等待队列没有节点,则将首节点指向node
firstWaiter = node;
else
//等待队列存在等待节点则将队列最后一个节点指向node
t.nextWaiter = node;
//更新尾节点指向node(新增加的节点插入到尾部)
lastWaiter = node;
return node;
}
复制代码
当前线程加入Condition等待队列过程图如下所示:
加入到等待队列后,会调用fullyRelease(node)释放同步状态(释放锁)并唤醒同步队列中的后继节点,我们来看下这个方法:
/**
* Invokes release with current state value; returns saved state.
* Cancels node and throws exception on failure.
* @param node the condition node for this wait
* @return previous sync state
*/
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取同步状态
int savedState = getState();
//释放同步状态并唤醒后继节点
if (release(savedState)) {
//释放同步状态成功
failed = false;
return savedState;
} else {
//释放同步状态失败则抛出异常
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//释放同步状态失败,则把node的waitStatus设置为Node.CANCELLED(表明线程已取消)
node.waitStatus = Node.CANCELLED;
}
}
复制代码
经过上述操作后,当前线程应该会进入等待状态,那么它是是怎么进入等待状态的那,我们看await方法中的这段代码:
while (!isOnSyncQueue(node)) {
//当前线程进入等待状态
LockSupport.park(this);
//等待过程中判断是否被中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
复制代码
我们看到while循环体条件isOnSyncQueue(node)会判断当前节点是否在同步队列中,不在同步队列则会进入循环体执行LockSupport.park(this)阻塞进入等待。而当该线程处于等待状态的时候被中断了,由于LockSupport.park(this)不会响应中断直接返回,则会执行下面判断是否中断的条件,如果为中断则会跳出循环。
由此我们可以知道跳出该循环体要么其它线程signal()或者signbalAll()将该线程加入到同步队列并unpark唤醒该等待线程。要么响应中断。
跳出上面循环体后,后续操作就是如果中断则响应处理中断,如果被唤醒没有中断则调用AQS的acquireQueued方法自旋尝试获取锁。
3.通知
调用Condition的sign方法,会将首节点移到同步队列中并使用LockSupport唤醒节点中的线程。
signal方法如下所示:
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
//判断当前线程是否是获得了锁的线程,不是的话则直接抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//首节点引用节点
Node first = firstWaiter;
if (first != null)
//等待队列存在节点则进行通知线程具体操作
doSignal(first);
}
复制代码
上面代码我们可以看到具体操作在doSignal方法中,我们来看下这个方法的源码:
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
//首节点指向节点为空,则表示没有节点在等待队列,则将尾节点引用置为空
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//将首节点指向节点置为空
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
复制代码
从上面代码我们可以看出主要是transferForSignal(first)通过首节点进行具体操作,我们继续来看下transferForSignal方法的源码:
/**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal)
*/
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//将waitStatus状态从等待状态更新为0,如果更新失败则表示该节点已经被取消(waitStatus为取消状态,正如前面等待介绍的,如果await的时候被中断则node的waitStatus被置为了Node.
CANCELLED)
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//将node添加到同步队列
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//添加到同步队列成功则唤醒等待线程,使得线程从LockSupport.park(this)返回
LockSupport.unpark(node.thread);
return true;
}
复制代码
我们可以看到该方法首先会更新线程的node节点的waitStatus为0(相当于判断当前线程是否被中断,中断则更新不成功直接返回false)。然后通过enq把节点在尾部新增到同步队列并唤醒等待队列。
其中enq把节点移动到同步队列的过程如下图所示:
通知还有另外一个singalAll方法:
/**
* Moves all threads from the wait queue for this condition to
* the wait queue for the owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
/**
* Removes and transfers all nodes.
* @param first (non-null) the first node on condition queue
*/
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
复制代码
singal()方法对等待队列中等待时间最长的节点(首节点)进行操作。singalAll()方法 从上面源码我们可以看到对队列中的每个节点都相当于执行了一次signal方法。将等待队列中的所有节点全部移动到同步队列,并唤醒每个节点的线程(进行锁的竞争)。
简单使用例子
public class ReentrantLockCondition implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
condition.await();
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockCondition tl = new ReentrantLockCondition();
Thread t1 = new Thread(tl);
t1.start();
Thread.sleep(2000);
//通知线程t1继续执行
lock.lock();
condition.signal();
lock.unlock();
}
}
复制代码
线程condition.await()后进入等待状态并释放锁,主线程调用condition.signal(调用后需要释放相关的锁),通知等待的线程继续执行。
参考书籍:《Java高并发程序设计(第2版)》《Java并发编程实战》《Java并发编程的艺术》