深入理解AbstractQueuedSynchronizerReentrantLock底层实现原理
Posted Dream_it_possible!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解AbstractQueuedSynchronizerReentrantLock底层实现原理相关的知识,希望对你有一定的参考价值。
目录
为什么说ReentrantLock是独占式的锁? 又是可重入的?
深入理解AbstractQueuedSynchronizer(一)_ Dream_it_possible!的博客-CSDN博客
上一篇文章主要介绍了AQS中的底层实现原理和结构,深入理解了条件等待队列和同步等待队列,总结一下上一篇的核心知识点:
1. Node和conditionObject的作用,Node定义了处于队列中的节点状态,定义了节点的模式,共享模式还是独占锁模式,同时实现了同步队列,conditionObject类定义了条件等待队列的firstWaiter和lastWaiter节点,实现了入队、唤醒等操作。
2. 条件等待队列是一个FIFO的单链表结构,因为插入节点的时候是从单链表中指针指向的最后一个节点之后插入的,出队是指针指向最后一个的一个节点移除,实则上是一个先进先出的队列结构。
3. 同步等待队列是一个双向的链表结构,用此结构能够快速的实现节点的移除和插入,因为抢占的时候处于队列后的节点能插入到第一个,如果用单链表结构,那么效率会比较低。
4. LockSupport提供唤醒线程的底层实现,由unpark(Thread)方法去唤醒线程。
5. 处于共享模式下的多线程竞争条件下,唤醒阻塞线程时,处于在条件队列的wait的线程需要出队进入到同步等待队列, 转队列由transferForSignal(Node node)方法实现。
6. 在一个线程使用资源后释放共享锁时,如果节点的状态为SINGAL会将节点的状态置为默认值0,如果同步队列的后续结点需要被唤醒,那么同步队列里的下一个节点唤醒,保证共享资源被完全释放掉。
基于AQS中常用的实现有很多,其中ReetrantLock、Semaphore、CyclicBarrier是常见的三种实现, 下面从源代码解释ReentrantLock是怎么基于AQS实现的。
ReentrantLock原理解析
ReetrantLock是基于AQS实现的一把独占式的锁,实现了JDKrt包里的Lock接口。
我们都知道一把锁最常用的就是加锁和解锁,下面跟着源码去追踪ReetrantLock中的Lock()方法和unLock()方法的底层实现原理, 提出一个问题?
为什么说ReentrantLock是独占式的锁? 又是可重入的?
Lock实现
首先看ReetrantLock类里的一个抽象的镜头内部类Sync, 该抽象类有一个抽象方法Lock()方法:
lock()抽象方法由Sync抽象类的2个实现类去实现,分别为:FairSync和UnFaiSync,默认实现是非公平锁,在ReentrantLock里的构造方法里实现:
因为默认是非公平的,我们先看非公平实现:
final void lock()
//如果state值为0,那么将state值置为1, 同时将当前线程设置到ExclusiveOWnerThread类里唯一拥有。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
lock()方法里只有一个if..else, 判断CAS机制判断AQS中的state值是否为0,使用CAS操作保证原子性,如果为0,那么将state值置为1,同时将当前线程设置到AbstractQueuedSynchronizer的父类AbstractOwnableSynchronizer类里exclusiveOwnerThread属性里,相当于是独占的意思。如果state值为1,那么调用acquire(1)方法,接着看acquire()的实现。
此处找到了为什么ReentrantLock是独占式的答案,因为在addWaiter时,添加的Node节点是EXCLUSIVE,同时用到了exclusiveOwnerThread标记了当前线程获取到的锁,一个线程对应一个exclusiveOwnerThread。
public final void acquire(int arg)
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//中断线程
selfInterrupt();
acquire()方法里就一个&&判断,如果成立,那么就执行selfInterrupt()方法,selfInterrupt方法比较好理解,就是条件成立那么该线程会被中断,不会继续执行,那我们把条件分解,接着看tryAcquire()方法,该方法在NonfairSync类实现:
进入到nofairTryAcquire()方法:
如果是处于一直加锁的状态不释放,那么过一段时间后一定会抛出Maximumlock count exceeded异常!当c的值超出了Int类型的最大数值时,那么c会变为负数,这个异常从另外一方面说明了ReentrantLock是可重入的,理解为一个线程对共享资源可以不断的加锁。
具体报错原因可以参考如下文章:
演示ReetrantLock锁的Maximum lock count exceeded问题_ Dream_it_possible!的博客-CSDN博客
小结
ReetrantLock的lock()实现原理主要包含以下几点:
1) 如果当前线程能直接拿到锁,那么会将state值置为1,然后设置currentThread。
2) 如果独占锁被其他线程给抢占了,那么其他线程会先通过addWaiter(Node.EXCLUSIVE), arg)方法进入到同步等待队列里。
3) 在同步等待队列的线程的Head节点会去再此执行tryAcquire()方法,该方法的最终返回是一个boolean类型,非公平锁的最终实现nonfairTryAcquire(int acquires)方法,每次加锁成功后state值为+1, 可以重复加锁,但要避免锁没有释放的问题。
4) 步骤3) 执行成功后,代表此线程加锁成功,同步队列会重新设置Head节点,然后将p.next=null, 帮助GC回收垃圾。
Unlock实现
unlock的实现相对于lock实现比较简单,直接调用了AbstractQueueSynchronizer里的release(int args)方法:
release方法的实现如下:
public final boolean release(int arg)
if (tryRelease(arg))
// tryRelease执行成功,那么唤醒Head的节点的下一个继承者
Node h = head;
if (h != null && h.waitStatus != 0)
// 进入到同步队列的线程默认状态是0,因此需要执行unpark操作确保唤醒
unparkSuccessor(h);
return true;
return false;
先看tryRelease, 当一个线程进入到此方法时,那么state的值会-1,由于state值是被volatile修饰的,能保证多线程环境下的可见性,tryRelease()方法原理其实很简单: 将共享的state值-1得到新值,然后将新值更新到state变量里。此处如果是其他线程Release的话,那么会抛出我们常见的IllegalMonitorStateException。
protected final boolean tryRelease(int releases)
// 来一个线程就将state-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 直到c,即state=0时,那么表示锁被完全释放掉。
if (c == 0)
free = true;
setExclusiveOwnerThread(null);
// 将新值更新到state里。
setState(c);
return free;
TryRelease()方法说白了就是讲自己线程加锁的state值-1, 直到state值为0,锁释放完毕, 继续看release()方法的逻辑 ,tryRelease执行成功后,将Head节点的下一个节点唤醒,因为进入到同步队列的线程默认状态是0,因此需要执行unpark,将waitStatus为SIGNAL的节点,即waitStatus的值为-1, 对SIGNAL的节点执行唤醒操作,这样的话能保证同步队列的所有线程都能够出队列,保证state值一直release到0为止,也就是确保共享资源被完全释放掉。
以上是关于深入理解AbstractQueuedSynchronizerReentrantLock底层实现原理的主要内容,如果未能解决你的问题,请参考以下文章