Reentrantlock 的实现原理

参考 Java并发之AQS详解 - waterystone - 博客园 https://www.cnblogs.com/waterystone/p/4920797.html

Reentrantlock与AQS

Reentrantlock的核心为AbstractQueuedSynchronizer(AQS), 抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch,它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。
AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可( isHeldExclusively()、 tryAcquire(int)、 tryRelease(int)、 tryAcquireShared(int)、 tryReleaseShared(int)),至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。

node状态

这里我们说下Node。Node结点是对每一个等待获取资源的线程的封装,其包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。变量waitStatus则表示当前Node结点的等待状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。

CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。

SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。

CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。

0:新结点入队时的默认状态。

注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。

简介AQS独占模式的acquire-release方法

acquire(int) ,此方法是独占模式下线程获取共享资源的顶层入口。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。 获取到资源后,线程就可以去执行其临界区代码了。

public final void acquire(int arg) {
  if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
}
  1. tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
  2. addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
  3. acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
  4. 如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

release(int) ,此方法是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;//找到头结点
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒等待队列里的下一个线程
        return true;
    }
    return false;
}

跟tryRelease(arg)和tryAcquire(arg)一样,这个方法是需要独占模式的自定义同步器去实现的。 正常来说,tryRelease()都会成功的,因为这是独占模式,该线程来释放资源,那么它肯定已经拿到独占资源了,直接减掉相应量的资源即可(state-=arg),也不需要考虑线程安全的问题。但要注意它的返回值,上面已经提到了,release()是根据tryRelease()的返回值来判断该线程是否已经完成释放掉资源了!所以自义定同步器在实现时,如果已经彻底释放资源(state=0),要返回true,否则返回false。
共享模式的 acquireShared-releaseShared和acquire-release很像,只不过是当前线程在获取资源后发现资源多余的情况会继续唤醒后继线程而已,其他基本一致。

Reentrantlock 的实现原理

内部的Sync继承AQS,而Sync有两个子类分别为NonfairSync和FairSync,分别对应非公平锁和公平锁的Sync,Sync重写了AQS的tryAcquire和tryRelease等方法。

加锁和释放锁的过程

Reentrantlock的加锁和释放锁分别执行Sync的sync.acquire(1)和sync.release(1),具体流程即为AQS的acquire(int)和release(int),只不过tryAcquire和tryRelease重写了而已。

AQS 中公平锁和非公平锁,可重入特性

对于非公平锁的tryAcquire流程,如果状态值为0,将状态值设为1,再将当前线程设为独占线程,否则如果当前线程已经是独占线程,则将状态值加1,此处反映了可重入特性,tryRelease则是以上操作的反操作。
对于公平锁 的tryAcquire,如果状态值为0,需要增加判断是否有前继线程,这里反映了公平调度特性,如果有且当前线程不是独占线程,则返回false,需要继续排队等待。

可中断特性

可中断和不可中断的区别在于:可中断在发生中断时直接抛出中断异常,而不可中断特性在发生中断时只是记录中断标记,在获取资源的时候再自行中断。线程在等待的时候可能被前继线程叫醒,也可能被中断叫醒,但是不一定能拿到资源,因为被异常叫醒可能前继节点还没拿到资源,被前继节点叫醒可能由于非公平调度资源被插队线程拿到,只有当前线程拿到资源后,才会根据中断标记判断是否重新触发中断。

原文地址:https://www.cnblogs.com/lllliuxiaoxia/p/15769401.html