前一篇文章ReentrantLock(一)讲了重入锁的的基本使用,一个线程在获取锁后只能通过释放锁才能让其它线程获取锁。如果一个线程在获取锁后,想要暂时把锁的控制权交出去,然后在适当时间再继续执行(线程间通信),用之前的方法就很难实现。今天我们来看一下重入锁的另一种用法——条件控制(Condition):
/**
* 用主线程和子线程交替执行50次,主线程先执行,每次执行100次打印,子线程后执行,每次执行10打印
*
*/
public class ConditionTest {
public static void main(String[] args) {
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
business.sub(i);
}
}
}).start();
for (int i = 1; i <= 50; i++) {
business.main(i);
}
}
static class Business {
private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private boolean bShouldSub = true;
public void sub(int i) {
lock.lock();
try {
while (!bShouldSub) {
try {
// add tailWaiter(condition) --> unpark next node.thread --> park current thread
condition.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false;
// add firstWaiter into tail(aqs)(unpark current thread)
condition.signal();
} finally {
lock.unlock();
}
}
public void main(int i) {
lock.lock();
try {
while (bShouldSub) {
try {
condition.await();
} catch (Exception e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 100; j++) {
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
condition.signal();
} finally {
lock.unlock();
}
}
}
}
condition.await()
/**
* 1. 创建一个Condition节点,并添加到Condition队列中
* 2. 释放当前线程持有的所有锁,并唤醒下一个节点线程
* 3. 挂起当前线程,等待signalled或interrupted
* 4. 如果被唤醒,就去尝试获取锁,如果获取不到,再次挂起
* 5. 如果有中断异常,就抛出异常
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 添加一个condition节点到condidtion等待队列队尾
Node node = addConditionWaiter();
// 释放当前线程所有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 如果没在同步队列就让线程挂起等待被唤醒
// 如果有中断或被唤醒就退出循环
while (!isOnSyncQueue(node)) {
LockSupport.park(this); // CONDITION节点肯定不在队列中,所以当前线程会挂起
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
// 如果线程被打断,就跳过循环去获取锁,反之,继续挂起
break;
}
// 运行到此处就说明线程已经被唤醒了,因为结束了循环
// 唤醒后,就自旋获取锁,同时判断是否中断
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理队列中状态不是Condition的节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0) // 被中断,抛异常
reportInterruptAfterWait(interruptMode);
}
/**
* 添加一个新节点到condition等待队列队尾(如果队列中有节点状态不是CONDITION,就先清除)
*/
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果最后一个节点状态不是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;
}
/**
* 释放当前线程拥有的所有锁,并唤醒下一个节点线程,如果释放失败,就将当前节点状态标记为CANCELLED
*/
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;
}
}
/**
* 判断节点是否在队列中
*/
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
// 如果节点状态为CONDITION,或没有父节点,则节点一定不在队列中
return false;
if (node.next != null) // 如果有子节点,则node节点一定在队列中
return true;
// 判断节点是否在等待队列中
return findNodeFromTail(node);
}
/**
* 前面已经把node节点放到了链表尾部排队了,此方法将当前线程挂起
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; // 标记是否成功拿到资源
try {
boolean interrupted = false; // 标记等待过程中是否被中断过
// CAS第二次自旋,直到获得锁
for (;;) {
final Node p = node.predecessor(); // 获取父节点
// 如果父节点是head,即当前节点是第二个节点,那么便有资格去尝试获取资源
// (可能是老大释放完资源唤醒自己,也可能被interrupt了)
if (p == head && tryAcquire(arg)) {
// 注意此处和读锁不同,这里不会唤醒下一个节点线程
setHead(node);
p.next = null; // 释放对子节点的引用,便于后面GC对子节点的回收,此时意味着head节点被移除了链表
failed = false;
return interrupted; // 返回等待过程中是否被中断过
}
if (shouldParkAfterFailedAcquire(p, node) &&
// 此处当前线程会挂起(park),等待前一个节点唤醒(unpark或interrupt)
parkAndCheckInterrupt())
// 注意此处有中断,还是会循环到上面去获取锁,其实是忽略了中断情况
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
condition.signal()
/**
* 将condition队列第一个有效节点转移到sync队列中
*/
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
// 从头节点开始唤醒,按添加顺序唤醒
doSignal(first);
}
/**
* 将condition队列第一个有效节点转移到sync队列中
*/
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
/**
* 将condition队列中第一个有效节点转移到sync队列中
*/
final boolean transferForSignal(Node node) {
// 如果设置失败,说明已经被取消了,没必要再进入Sync队列了,doSignal中的循环会找到下一个node再次执行
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/**
* 如果上面执行成功,则将node加入到Sync队列中,等待unlock方法去唤醒,
* enq会返回node的父节点p,
* 这里if判断只有在p节点是取消状态或p节点状态为SIGNAL失败时才会执行unpark
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}