ReentrantLock 源码分析

北城以北 提交于 2020-04-26 01:54:36

前言

上一篇文章 ReentrantLock 源码分析 - 基础篇 AbstractQueuedSynchronizer(AQS) 的相关 概念作用 作了简要介绍, 本文将继续对 AQS 做进一步介绍 (基于 jdk1.8 源码)。

阅读之前需要了解什么是 共享锁独占锁 .

正文

上一篇有提到 CountDownlatch, ReentrantLock, ReentrantReadWriteLock 等并发包工具类都是基于 AQS 实现的, 这要求 AQS 需要提供很 丰富 并且 通用 的功能,

比如:

ReentranLock是独占锁, 而 ReentrantReadWriteLock是共享锁, 他们分别调用 AQSacquire 方法去获取独占资源, acquireShared 方法去获取共享资源。但是又共同调用了 compareAndSetState 方法去实现 CAS 操作。

这还只是这两个类的之间的区别, 而 java.util.concurrent包下面有诸多工具类,

因此, 我们暂时没必要全面了解 AQS 的所有功能

那么, 接下来就着重分析 AQSReentrantLock的作用。

先来看一张 AQS 的类图

先来看四个类

从图中可以看出 :

  • AQS 继承自 AbstractOwnableSynchronizer
  • AQS 有两个内部类 ConditionObjectNode

对于研究 ReentrantLock, 我们暂时只需要关注 Node 类, 和 AQS 中的部分 属性 和 方法

1. Node 类

在这里插入图片描述

上图为 Node的所有属性

  • prevnext 分别为前、后指针
  • thread 存放的是线程对象
  • SHARED 标记当前线程是因为抢夺共享资源,而被放入 AQS 队列的
  • EXCLUSIVE 标记当前线程是因为抢夺独占资源, 而被放入 AQS 队列的

上一篇文章 ReentrantLock 源码分析 - 基础篇 有提到, AQS 是一个双向队列, 队列中的元素就是 Node对象 在这里插入图片描述

2. AQS 的几个属性

    // 指向队列的头
	private transient volatile Node head; 
   
    // 指向队列的尾
    private transient volatile Node tail; 
    
    // 上一篇也有讲到在 ReentrantLock 中的作用,表示重入次数
    private volatile int state; 

3. AQS 的几个方法

  • acquire(int arg)
  • release(int arg)
  • compareAndSetState(long expect, long update)

3.1 acquire(int arg)

获取独占资源

该方法大致的逻辑是:

尝试去获取锁, 如果获取锁失败,就把当前线程放入队列, 并把当前线程挂起

public final void acquire(int arg) {
     // tryAcquire 尝试去获取锁, 而 !tryAcquire 则表示获取锁失败
     // Node.EXCLUSIVE 上面有讲到是标记当前线程是获取独占资源失败
     // addWaiter 放入 AQS 队列
     // acquireQueued 这里可以先简单理解为 阻塞住了
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
        selfInterrupt();
    }
}

其中 tryAcquire, addWaiter, acquireQueued三个方法内部具体逻辑, 下一篇文章会继续深入讲解。

3.2 release(long arg)

释放独占资源

该方法大致的逻辑是:

当一个线程调用 release方法时, 会调用 tryRelease尝试释放资源(通过设置 state属性), 然后通过unparkSuccessor最终调用 LockSupport.unpark方法去激活 AQS 队列中的被阻塞的线程。

    public final boolean release(long arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
3.2.1 tryAcquire 和 tryRelease 方法

上一篇有提到 AQS 是一个抽象类, tryAcquire 和 tryRelease 两个方法在这个抽象类中并没有具体实现。它们是交给具体的子类来实现的,。

ReentrantLock中, 当 state为 0 时,表示锁未被占用, 为 1 时,表示被占用; 而其内部的tryAcquire方法, 会使用 CAS 操作判断当前的 state是否为 0,

  • 如果是, 就通过 CAS操作,将 state设为 1, 并设置当前锁的拥有者为当前线程, 然后返回 true
  • 如果 CAS 操作失败,就返回 false.

相反的, ReentrantLock内部在实现tryRelease方法时, 会使用 CAS 操作把 state 的值从 1 修改为 0, 并设置当前锁的拥有者为 null, 然后返回 true, 如果 CAS 操作失败,就返回 false.

3.3 compareAndSetState(long expect, long update)

该方法直接调用了 unsafe 对象的 native 方法, 提供了 CAS 操作, 这一点在上一篇文章 ReentrantLock 源码分析 - 基础篇 中也有提到。

    protected final boolean compareAndSetState(long expect, long update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapLong(this, stateOffset, expect, update);
    }

总结

ReentrantLock, CountDownlath 等并发工具类有其各自的作用, 而 AQS 是实现这些不同作用的基础,。

AQS 本质上是一个 双向队列, 当多个线程通过 ReentrantLock 对象的 lock()方法同时去 尝试抢占锁 时, 未成功获取锁的线程会被放入 AQS 队列 中去排队等待,

而尝试去抢占锁的方法 tryAcquire是一个抽象方法, 由继承 AQS 的子类去自行实现, tryAcquire内部通常会通过 CAS 操作去判断获取锁的结果。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!