AbstractQueuedSynchronizer源码分析(ReentrantLock锁的实现)
1. 前言
Java中好多地方用到AbstractQueuedSynchronizer(PS:简称AQS),比如ReentrantLock、线程池,这部分在面试的时候也经常被问到,今天以ReentrantLock为例,通过源码来加深对AQS的理解
2. lock
通常,我们的用法是这样的:
那么lock(),unlock()到底做了什么,我们并不知晓,接下来一步一步揭开她的神秘面纱
2.1. lock
可以看到,ReentrantLock默认是NonfairSync(非公平锁)
2.2. NonfairSync
可以看到,首先判断同步状态是否是0,如果是0则临界资源没有被占用,并且将状态设为1,当前线程获得这个资源,可以访问;否则,调用acquire(1)方法尝试获取资源的访问控制权。
2.3. tryAcquire
尝试再获取一次
如果当前同步状态是0,则跟之前相同,将状态置为1,当前线程获得访问权,返回true
如果当前资源的所有者线程就是当前线程,则状态加1,当前线程仍然获得访问权,返回true
如果以上情况都不是(PS:资源被别的线程占用着),则返回false
2.4. acquireQueued
如果上一步tryAcquire方法返回false,则继续调用acquireQueued方法将线程添加到队列中(PS:链表的尾部)
在添加到链表之前,先封装成Node对象
将当前线程构造成Node对象,节点的模式是排它的,然后将其加到链表的尾部(或是说叫队列),最后将该结点返回
这里有两个特殊的节点:等待队列的头结点 和 等待队列的尾结点
接下来,将刚才构造的节点加到队列中
如果当前节点的前驱节点是头结点(head),并且当前节点再次尝试获取资源(tryAcquire方法),恰好成功了,于是皆大欢喜
如果当前节点的前驱节点既不是head,而且当前节点也没有抢占到资源,则循环直到前驱节点的状态变成SIGNAL,则挂起当前线程
3. unlock
如果资源的所有者线程不是当前线程的话,则抛出异常
如果当前资源同步状态减1恰好是0,则成功释放,同步状态置为0,资源所有者线程置为null,唤醒head的后继节点
4. 小结
加锁
1、默认非公平锁(PS:当前线程不是直接加到等待队列中,而是先尝试获取一次,如果不成功则加到队列中,在加的时候还会尝试一次)
2、临街资源有一个同步状态,0表示当前没有线程占用,则可以直接可以获取到资源(加锁成功)
3、如果资源所有者线程就是当前线程,则状态加1,仍然可以获得锁
3、加锁成功以后,将同步状态置为1,资源所有者线程置为当前线程
4、不成功,则封装成Node加入到等待队列(链表)中,此时,还会像前面一样再尝试(抢占)一次
5、抢占不成功,加到等待队列中,线程挂起
解锁
1、资源所有者线程不是当前线程,则抛异常
2、唤醒下一个结点