1、AQS
AQS:AbstractQueuedSynchronizer,抽象队列同步器。
在Java并发包中提供的锁(java.util.concurrent.locks),都是利用 AQS 来实现的。AQS底层其实也是利用 CAS 来共同实现锁的机制。
AQS 内部核心的参数:
- state:用于记录锁的同步状态,AQS底层的核心字段。但是,在ReentrantLock、ReentrantReadWriteReadLock的用法又是不太一样的。
- head:如果线程获取锁失败,会进入队列等待,其中head指针就是指向等待队列的对头。
- tail:tail指针指向等待队列的队尾。
head 指针和tail指针的类型都是 Node,Node是AQS的静态内部类,用于实现等待队列。Node主要核心参数有:waitStatus-等待状态、prev指针、next指针、thread-指向等待线程。因为有prev指针和next指针,所以这个等待队列是双向列表。
- 各种offset:stateOffset、headOffset、tailOffset、waitStatusOffset、nextOffset。这些offset主要用于CAS操作更新字段值,每个offset会记录对应字段在类中的偏移量。在AQS中,会利用CAS来实现无锁化更新字段值。
- unsafe:Unsfase 实例,用于执行CAS操作来更新字段值。
- exclusiveOwnerThread:这个是AQS的父类(AbstractOwnableSynchronizer)的字段,用于标识当前成功持有锁的线程。
值得注意的是,不管是state变量、head指针,还是tail指针,他们都是利用 volatile 来修饰,来保证多线程访问中变量的可见性和一致性。
2、ReentrantLock
ReentrantLock 中有一个抽象静态内部类:Sync,它继承了 AQS,重写了AQS的某些方法:如tryRelease、isHeldExclusively。
接着,因为ReentrantLock提供了公平锁和非公平锁,所以内部有两个静态内部类,都是实现了抽象静态内部类 Sync:NoFairSync和FairSync,分别代表非公平锁和公平锁。
不管是 FairSync 还是 NoFairSync,都是利用 AQS 中的 state 来表示锁的持有状态。只要 state >= 1,即现在已经有线程在持有锁,否则等于0就是没有线程持有锁。当线程成功持有锁,即成功将 state 设置成1,那么就会将 exclusiveOwnerThread 指向当前线程,这样也可以支持锁可重入,只要获取锁的线程就是 exclusiveOwnerThread 指向的线程,那么就直接给 state 再加1就可以了,来表示当前线程再次成功获取锁。
2.1、lock() 方法加锁
不管是 ReentrantLock 中,公平锁和非公平锁的加锁逻辑会有点不一样。
非公平锁=NoFairSync:会直接尝试利用CAS将 state 从0设置成1,如果设置成功,则表示获取锁成功,此时会将 exclusiveOwnerThread 指向当前线程。否则,会调用 acqiure(1) 方法,底层是调用 AQS 的 acquire 方法。
公平锁=FairSync:直接调用 acquire(1) 方法,底层也是调用 AQS 的 acquire 方法。
AQS 的 acquire 方法:
1、tryAcquire 方法尝试获取锁
方法比较好理解,就是调用 tryAcquire(int arg) 方法去尝试获取锁,而这个 tryAcquire 方法需要 NoFairSync 和 FairSync 自行实现。
在 ReentrantLock 中,公平锁和非公平锁的加锁逻辑是有点不太一样的。
首先是NoFairSync:非公平锁最后是调用自身的 nonfairTryAcquire 方法,里面逻辑很简单。
- 获取当前锁的 state
- 如果等于0,尝试利用 CAS 从0设置成1,如果成功,设置 exclusiveOwnerThred为当前线程,然后返回true
- 否则,判断当前线程是否等于 exclusiveOwnerThread,如果等于,则给 state 增加1,表示可重入锁次数+1,然后返回true
- 最后都不行就返回false表示获取锁失败。
接着是FairSync:这里和非公平锁不同的一点是,如果state=0,还需要判断等待队列中是否有线程在等待,如果没有才可以去尝试利用 CAS 去将state从0设置成1,如果成功就设置 exclusiveOwnerThread 为当前线程,然后返回true表示获取锁成功。如果state不等于0,和非公平锁一样,进行重入的逻辑。
2、获取锁失败,调用 addWaiter 方法加入等到队列队尾
如果获取成功,那么就直接跳出if分支了;否则表示获取锁失败,会调用 addWaiter 方法进入等待队列,然后调用 acquireQueued 来进入死循环等待获取锁。
3、调用 acquireQueued 进入死循环等待获取锁
但是在 acquireQueued 方法中,肯定不是一直循环获取锁啦,在死循环中会有下面额逻辑
- 当前节点的前一个节点是否为head节点(注意,在AQS中,头节点永远是一个空节点,不适等待线程对应的节点,等待线程的节点都是从第二个开始),如果是的话,就调用 tryAcquire(int arg) 方法去尝试获取锁,如果获取成功,将当前线程从等待队列中移除,然后跳出循环。
- 否则,最终会调用 AQS 的 parkAndCheckInterrupt() 方法,里面会调用 LockSupport#park 方法将当前线程阻塞进入等待状态。(当有线程释放锁,就会唤醒等待队列中的队头,即第一个等待线程)。
2.2、unlock() 方法释放锁
ReentrantLock 释放锁就不分公平和非公平了,所以 ReentrantLock 的释放锁是直接调用 AQS 的 release(int arg) 方法。方法很简单,下面讲解一下。
- 调用 tryRelease 方法尝试释放锁,这个是 AQS 的子类去实现。
- 如果等待队列中存在等待线程,那么就唤醒第一个等待线程。
ReentrantLock的tryRelease方法
这里很简单,只是将state-1,并且要判断是否存在重入锁的情况。如果不存在,将 exclusiveOwnerThread 设置为null,然后直接返回true表示释放锁成功。否则只是将state-1,返回返回false,表示这个锁还没真正被释放掉。