六、LockSupport 工具
LockSupport定义了一组以park开头的方法来阻塞当前线程,以及unpart(Thread thread)方法来唤醒线程。
在Java 6 中,LockSupport增加了三个方法:用于替代原来的方法
part(Object blocker)、parkNanos(Object object,long nanos)、partUntil(Object blocker,long deadline)
用于阻塞当前线程的功能,从那时blocker是用来标识当前线程在等待的对象(即阻塞对象),该对象主要用于问题排查和系统监控。
LockSupport详解可看:https://www.jianshu.com/p/ceb8870ef2c5
七、Condition接口
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。
1、Condition接口与示例
Condition定义了等待/通知两种类型的方法,当前对象调用这些方法时,就需要先获取Condition对象。Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。代码示例一下:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();//通过lock对象创建condition对象
public void conditionWait() throws InterruptedException{
lock.lock();
try{
condition.await();
}finally{
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException{
lock.lock();
try{
condition.signal();
}finally{
lock.unlock();
}
}
一般都会把Condition对象作为成员变量,当调用await()方法后,当前线程会释放锁并在此等待。当其他线程调用Condition对象的signal()方法,通知当前线程后,当前线程才能从await()方法返回,冰倩在返回之前已经获得了锁。
关于Condition定义的部分方法如下:
再通过一个有界队列的实例来了解Condition:
有界队列,当队列为空时,队列的获取操作将会阻塞获取线程,知道队列中有新增元素,当队列已满时,队列的插入操作将会阻塞插入线程,直到队列出现“空位”。
public class BoundedQueue<T> {
private Object[] items;
private int addIndex, removeIndex, count;//添加的下标,删除的下标和数组当前的数量
private Lock lock = new ReentrantLock(); //获取Lock对象
private Condition notEmpty = lock.newCondition(); //通过Lock对象来创建Condition对象
private Condition notFull = lock.newCondition();
public BoundedQueue(int size) {
items = new Object[size];
}
//添加一个元素,如果数组满,则添加线程进入等待状态,直到有空位
public void add(T t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {//如果数组满,阻塞添加线程
notFull.await();
}
items[addIndex] = t;
if (++addIndex == items.length) {
addIndex = 0;
}
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
// 由头部删除一个元素,如果数组为空,则删除线程进入等待状态,直到有新添加元素
public T remove() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object x = items[removeIndex];
if (++removeIndex == items.length) {
removeIndex = 0;
}
--count;
notFull.signal();
return (T) x;
} finally {
lock.unlock();
}
}}
当数组数量等于数组长度时,说明数组已经满了,则调用notFull.await(),当前线程随之释放锁并进入等待状态。
当数组数量不等于数组长度时,表示数组未满,则添加元素到数组中,同时通知等待在notEmpty上的线程,数组中已经有新元素可以获取。
3、Condition的实现分析
ConditionObject是AQS的内部类,每个Condition对象都包含着一个FIFO队列,该队列是Condition对象通知/等待功能的关键。在队列中每一个节点都包含着一个线程引用,该线程就是在该Condition对象上等待的线程。
一个Condition包含一个等待队列,Condition拥有首节点和尾节点。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列:
Condition拥有尾节点的引用,所以新增节点的时候只需要将原来的尾节点nextWaiter指向它,并更新尾节点即可。
一个对象拥有一个同步队列和一个等待队列,而同步器拥有一个同步队列和对个等待队列:
等待:
调用Condition的await()方法(或者以await开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await()方法返回时,当前线程一定获取了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;
/**
* 检测此节点的线程是否在同步队上,如果不在,则说明该线程还不具备竞争锁的资格,则继续等待
* 直到检测到此节点在同步队列上
*/
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.
//Node的节点状态如果不为CONDITION,则表示该节点不处于等待状态,需要清除节点
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//将该节点加入到条件队列中最后一个位置
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
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;
}
}
//isOnSyncQueue(Node node):
//如果一个节点刚开始在条件队列上,现在在同步队列上获取锁则返回true
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
signal():
- 判断当前线程是否已经获取了锁,如果没有获取则直接抛出异常,因为获取锁为通知的前置条件。
- 如果线程已经获取了锁,则将唤醒条件队列的首节点
- 唤醒首节点是先将条件队列中的头节点移出,然后调用AQS的enq(Node node)方法将其安全地移到CLH同步队列中
- 最后判断如果该节点的同步状态是否为Cancel,或者修改状态为Signal失败时,则直接调用LockSupport唤醒该节点的线程。
/*该方法的前置条件是当前线程获得了锁,接着获取等待队列的首节点,
将其移动到同步对列并使用LockSupport唤醒节点中的线程。
移动首节点是通过enq(Node node)方法
*/
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) &&//将老的头结点,加入到AQS的等待队列中
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//将该节点从状态CONDITION改变为初始状态0,
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将节点加入到syn队列中去,返回的是syn队列中node节点前面的一个节点
Node p = enq(node);
int ws = p.waitStatus;
//如果结点p的状态为cancel 或者修改waitStatus失败,则直接唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
总结:
一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到条件队列中,然后释放锁,最后通过isOnSyncQueue(Node node)方法不断自检看节点是否已经在CLH同步队列了, 如果是则尝试获取锁,否则一直挂起。
当线程调用signal()方法后,程序首先检查当前线程是否获取了锁, 然后通过doSignal(Node first)方法唤醒CLH同步队列的首节点。 被唤醒的线程,将从await()方法中的while循环中退出来, 然后调用acquireQueued()方法竞争同步状态。
借鉴博客:https://www.jianshu.com/p/5d83a8c6a6f3