Condition 源码解析
文章目录
- Condition 源码解析
- 一、Condition
- 二、Condition 源码解读
- 2.1. lock.newCondition() 获取 Condition 对象
- 2.2. condition.await() 阻塞过程
- 2.3. condition.signal() 唤醒过程
- 2.4. condition.await() 被唤醒后
- 三、总结
一、Condition
在并发情况下进行线程间的协调,如果是使用的 synchronized
锁,我们可以使用 wait()/notify()
进行唤醒,如果是使用的 Lock
锁的方式,则可以使用 Condition
进行针对性的阻塞和唤醒,相较于 wait()/notify()
使用起来更灵活。那么 Condition
是如何实现线程的等待和唤醒的呢,本文通过解析Condition
的源码进行理解。
在进行源码分析前,先通过一个案例看下 Condition
是如何使用的
public class Test {
public synchronized static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
System.out.println("线程1开始等待!");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1被唤醒继续执行结束!");
lock.unlock();
}, "1").start();
new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println("开始唤醒线程!");
condition.signal();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行结束!");
lock.unlock();
}, "2").start();
}
}
运行之后,可以看到下面日志:
由于在第一个线程中,使用的 condition.await()
因此当前线程会被阻塞挂起,而第二个线程,在 1s
后进行了 condition.signal()
操作,因此第一个线程会被唤醒继续执行。这里可以发现,第一个线程阻塞时锁并没有释放,而第二个线程在1s
后也成功拿到锁了,所以表明在 condition.await()
时会自动释放当前锁,这点和 wait()
相同,在第二个线程进行了 condition.signal()
操作,第一个线程并没有继续向下执行,而是等待第二个线程处理完才会继续执行,由此可以表明被唤醒的线程会重新获取锁,成功获取锁后继续执行。
下面通过源码看下 Condition
是如何实现的等待唤醒。
二、Condition 源码解读
2.1. lock.newCondition() 获取 Condition 对象
首先看下在使用 lock.newCondition()
获取一个Condition
对象时,具体做了什么,这里以 ReentrantLock
为例,进入到 ReentrantLock
的 newCondition()
方法中,又执行了 Sync
的 newCondition()
方法,再进去就会发现其实是 new
了一个 ConditionObject
类对象:
下面点到这个类中,可以看到其实是 AQS
下的一个子类:
2.2. condition.await() 阻塞过程
了解到 Condition
的对象后,可以看到是 AQS
下的一个子类,那下面其他的方法也肯定依赖于 AQS
,下面看下 condition.await()
方法,点到 await()
方法中:
其中 addConditionWaiter()
则是将自己加入到 AQS
的队列中,并获取到当前线程所在的 Node
,这里注意下 Node
的状态是 Node.CONDITION
也就是 -2
,后面会依赖于该状态。
再回到 await()
方法继续向下看,接着使用了 fullyRelease()
方法传入了当前的 Node
,这里的 fullyRelease()
方法主要做了释放当前线程锁的操作,可以看到又调用了 AQS
的 release()
进行释放资源,也就是释放了当前所持有的锁。
回到 await()
方法中,当释放锁后,下面进入到了 while
循环中,通过查看 isOnSyncQueue()
方法,可以看到是符合while
的条件也就可以进入到循环中:
在循环中可以明显的看到 LockSupport.park(this)
,将当前线程进行了阻塞。
2.3. condition.signal() 唤醒过程
上面已经看到线程被阻塞了,如果需要被唤醒则需要通过condition.signal()
,这个方法是如何唤醒的呢?
下面来到 AbstractQueuedSynchronizer
类的 signal()
方法中:
主要执行了 doSignal()
方法,再点到 doSignal()
中,可以看到这里开启了一个循环,对链表的每一个元素都进行了 transferForSignal()
操作,这里也比较好理解,就是要唤醒等待中的线程。
下面点到 transferForSignal()
中,看下对每个 Node
都做了什么操作。点进去之后也比较好理解,如果状态是 Node.CONDITION
也就是 -2
,刚才在解读 await()
方法时就提到这个状态了,这里正好形成了呼应,下面有个非常显眼的操作 LockSupport.unpark(node.thread)
直接唤醒了目标线程。也就是唤醒了 2.2 中的最后一步操作。
2.4. condition.await() 被唤醒后
当 await()
方法中的 LockSupport.park(this)
被唤醒后,继续向下执行,下面会判断下当前线程有没有被打断,如果没被打断则 break
终止循环继续执行。
下面会使用 AQS
的 acquireQueued()
方法,将先进入队列的线程进行抢占锁资源,如果成功获取锁后就会继续执行,如果抢占失败则继续被挂起阻塞。
三、总结
通过上面的源码分析,应该对 Condition
有了新的理解和掌握,在源码中好多地方都使用了 CAS
,因此当竞争资源非常激烈时, Lock
的性能要远远优于 synchronized
。