本篇我们只介绍LockSupport与Condition的部分API和一些概念,不涉及示例,如果想要示例,可以查看JDK源码的阻塞队列部分,我也写了一篇阻塞队列的文章也可以查看。
在Java线程状态与生命周期一篇我们介绍Java的声明周期时介绍过在调用LockSupport的方式时会让线程处于WAITiNG状态和返回RUNNABLE状态。LockSupport定义了一组的公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能,它是构建同步组件的基础工具。LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread thread) 方法来唤醒一个被阻塞的线程。如下表为LockSupport提供的API方法:
API方法 |
描述 |
void park() |
阻塞当前线程,调用void unPack(Thread thread)或者线程中断才返回 |
void parkNanos(long nanos) |
在void park()的基础上添加了阻塞时间,超时也会返回,阻塞最长不超过nanos秒 |
void parkUntil(long deadline) |
在void park()的基础上添加了阻塞时间,超时也会返回,阻塞最长不超过deadline这个时间点。 |
void park(Object blocker)
扫描二维码关注公众号,回复:
12069396 查看本文章
|
这是三个方法以上面三个一样,唯一不同的时添加了blocker是用来标识当前线程在等待的对象(阻塞对象) |
void parkNanos(Object blocker,long nanos) |
|
void parkUntil(Object blocker,long deadline) |
|
void unPack(Thread thread) |
唤醒处于等待状态的线程 |
LockSupport的的使用较为简单,相对于LockSupport的使用,我们用的更多的则是Condition接口,在介绍synchronized时,我们已经知道synchronized结合wait()、 wait(long timeout)、notify()以及notifyAll()方法可以实现等待通知机制,它的实现主要是由对象的监视器实现。同样Condition也实现了等待通知机制,不过它使用的是等待队列的方式实现的,这里又用到了AQS的内容。如下为synchronized与Condition接口实现的对比:
使用Condition接口时,必须通过Lock的newCondition方法获取,后面我们会讲解Lock的newCondition方法,这里我们先介绍Condition接口提供的方法,如下表为Condition接口提供的方法:
API方法 |
描述 |
void await() |
进入等待状态直到被通知,或者中断。 |
void awaitUninterruptibly() |
进入等待状态直到被通知,对中断不敏感 |
long awaitNanos(long nanosTimeout) |
当前线程进入等待状态直到被通知。中断、或者到超时,如果返回0或者负数表示超时,否则返回消耗的时间。 |
boolean await(long time, TimeUnit unit) |
与awaitNanos(long nanosTimeout)一样 |
boolean awaitUntil(Date deadline) |
当前线程进入等待状态直到被通知。中断、或者到某个时间点,如果没有指定时间点就被通知返回true 否则返回false |
void signal() |
唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁 |
void signalAll() |
唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关的锁 |
我们说在获取Condition时,必须获取到锁,我们通过ReentrantLock的newCondition方法查看Lock是如何获取到Condition实例的,代码如下:
final ConditionObject newCondition() {
return new ConditionObject();
}
上面的代码中返回了一个ConditionObject实例,ConditionObject是Condition的一个实现,它是AQS的一个内部类。每Condition对象都包含着一个队 列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。下面结合源码分析Condition的实现,主要包括三个方向分别为:等待队列、等待和通知。
首先我们看Condition的等待队列,了解过AQS的或许对于等待队列已经有一定的了解,虽然Condition是AQS的子类,但是Condition的等待队列与AQS的等待队列有着一些区别。如下为AQS中对ConditionObject的定义:
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
//队列中的第一个等待节点
private transient Node firstWaiter;
//队列中最后一个等待节点
private transient Node lastWaiter;
//构造方法
public ConditionObject() { }
......
}
一个Condition包含一个等待队列,Condition拥有首节点(firstWaiter)和尾节点 (lastWaiter)。对于Node的定义可以参考AQS的一篇博客:队列同步器 AQS原理解析—独占式获取同步状态。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部 加入等待队列,如下为Condition的等待队列结构图:
如图所示,Condition拥有首尾节点的引用,而新增节点只需要将原有的尾节点nextWaiter 指向它,并且更新尾节点即可。与同步队列不同的是一个Lock实例也就是AQS实例,可以有多个Condition等待队列,如下图为AQS的同步队列与Condition队列结构图:
我们实在调用Condition以await开头的方法时,会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。我们从源码的角度以await()为例分析Condition的等待过程,代码如下所示:
public final void await() throws InterruptedException {
//从线程中断抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//将节点添加到等待队列
Node node = addConditionWaiter();
//释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果不是同步队列类型的节点,将线程改为WAITING状态,
//下面添加的节点的状态为Condition类型,不是同步类型
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);
}
//将节点添加到等待队列
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建节点实例,类型为Condition
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
调用该方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态。同步队列的首节点并不会直接加入等待队列,而是通过addConditionWaiter()方法把当前线程构造成一个新的节点并将其加入等待队列中。其加入队列的结构图如下:
调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。我们以signal()为例如下为源代码:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
}while (!transferForSignal(first) &&(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//如果不能改变节点状态,取消
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//家人到同步队列
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//唤醒线程
LockSupport.unpark(node.thread);
return true;
}
signal()方法进行了isHeldExclusively()检查,检查当前线程是否是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移动到同步队列。当节点移动到同步队列后,当前线程再使用LockSupport唤醒该节点的线程。如下为唤醒的结构图:
LockSupport与Condition的等待通知机制就介绍到此,如果想要使用示例,建议阅读阻塞队列部分源码,因为阻塞队列源码部分大量使用了Condition等待通知机制和LockSupport。这里就不在列举任何例子了。