简介
AQS实现锁机制并不是通过synchronized——给对象加锁实现的,事实上它仅仅是一个工具类!它没有使用更高级的机器指令,也不靠关键字,更不依靠JDK编译时的特殊处理,仅仅作为一个普普通通的类就完成了代码块的访问控制。
AQS使用标记位+队列的方式,记录获取锁、竞争锁、释放锁等一些类锁操作。但更准确的说,AQS并不关心什么是锁,对于AQS来说它只是实现了一系列的用于判断资源是否可以访问的API,并且封装了在访问资源受限时,将请求访问的线程加入队列、挂起、唤醒等操作。AQS关心的问题如下:
- 资源不可访问时,怎么处理?
- 资源时可以被同时访问,还是在同一时间只能被一个线程访问?
- 如果有线程等不及资源了,怎么从AQS队列中退出?
至于资源能否被访问的问题,则交给子类去实现。
站在使用者的角度,AQS的功能主要分为两类:独占锁和共享锁。在它的所有子类中,要么实现了它的独占功能的API,要么实现了共享功能的API,但不会同时使用两套API,即使是ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别使用两套API来实现的。
- 当AQS的子类实现独占功能时,如ReentrantLock,资源是否可以被访问被定义为:只要AQS的state变量不为0,并且持有锁的线程不是当前线程,那么代表资源不可访问。
- 当AQS的子类实现共享功能时,如CountDownLatch,资源是否可以被访问被定义为:只要AQS的state变量不为0,那么代表资源不可以为访问。
AQS类继承结构图
独占锁
ReentrantLock是AQS独占功能的一个实现,通常的使用方式如下:
reentrantLock.lock();
// do something
reentrantLock.unlock();
ReentrantLock会保证执行do something
在同一时间有且只有一个线程获取到锁,其余线程全部挂起,直到该拥有锁的线程释放锁,被挂起的线程被唤醒重新开始竞争锁。
ReentrantLock的加锁全部委托给内部代理类完成,ReentrantLock只是封装了统一的一套API而已,而ReentrantLock又分为公平锁和非公平锁。
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
- 公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序,并依此顺序获得锁,类似于排队吃饭;
- 非公平锁:每个线程抢占锁的顺序不变,谁运气好,谁就获得锁,和调用lock方法的先后顺序无关,类似后插入。
换句话说,公平锁和非公平锁的唯一的区别是在获取锁的时候是直接去获取锁,还是进入队列排队的问题。
获取独占锁的流程
FairSync的tryAcquire()
方法分析
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();/*获取当前线程*/
int c = getState(); /*获取父类AQS中的标志位*/
if (c == 0) {
if (!hasQueuedPredecessors()
/*说明队列中没有其他线程:没有线程正在占有锁,那么修改一下状态位*/
//注意:这里的acquires是在lock的时候传递来的,从上面的图中可以知道,这个值是写死的1
&& compareAndSetState(0, acquires)) {
// 如果通过CAS操作将状态为更新成功则代表当前线程获取锁,
// 因此,将当前线程设置到AQS的一个变量中,说明这个线程拿走了锁。
setExclusiveOwnerThread(current);
return true;/*返回true,说明已拿到锁*/
}
} else if (current == getExclusiveOwnerThread()) {
/*如果当前state不为0,说明锁已经被拿走,那么判断是否是当前线程拿走了锁*/
/*因为锁是可重入的,可以重复lock,unlock*/
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如果当前线程获取到锁,tryAcquire()
返回true,否则返回false,这时会返回到AQS的acquire()
方法。
如果没有获取到锁,那么应该将当前线程放到队列中去,只不过,在放之前,需要做些包装。
AQS的addWaiter()
方法分析
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
/*将节点加入到队列尾部*/
node.prev = pred;
/*尝试使用CAS方式修改尾节点,但是在【并发情况】下,可能修改失败*