第一次听说AQS,是滴滴的电话面试,之前毫无了解。后来看了几篇博客,也是云里雾里。后来看源码,事半功倍。现在做一些简单的总结。
J.U.C是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。
核心数据结构:双向链表 + state(锁状态)
底层操作:CAS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AQS中的int类型的state值,这里就是通过CAS(乐观锁)去修改state的值。lock的基本操作还是通过乐观锁来实现的。
获取锁通过CAS,那么没有获取到锁,等待获取锁是如何实现的?我们可以看一下else分支的逻辑,acquire方法:
- tryAcquire:会尝试再次通过CAS获取一次锁。
- addWaiter:通过自旋CAS,将当前线程加入上面锁的双向链表(等待队列)中。
- acquireQueued:通过自旋,判断当前队列节点是否可以获取锁。
可以看到,当当前线程到头部的时候,尝试CAS更新锁状态,如果更新成功表示该等待线程获取成功。从头部移除。
基本可以确认,释放锁就是对AQS中的状态值State进行修改。同时更新下一个链表中的线程等待节点。
可以看到在整个实现过程中,lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)。因此在非必要的情况下,建议使用synchronized做同步操作。
2019-04-18补充:
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
2019-07-19
这篇文章基本上就是搬运,非常粗糙。其实原文也没有交代太多细节,但是AQS的基本设计思想却写清楚了。