博客
关于我
ReentrantLock 源码分析 - AbstractQueuedSynchronizer 详解(一)
阅读量:480 次
发布时间:2019-03-06

本文共 3045 字,大约阅读时间需要 10 分钟。

前言

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

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

正文

上一篇有提到 CountDownlatch, ReentrantLock, ReentrantReadWriteLock, Semaphore等并发包工具类都是基于 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 队列的
  • waitStatus 表示当前线程等待状态, 可选值为:
    • CANCELLED 线程被取消了
    • CONDITION 线程在条件队列中等待
    • SIGNAL 线程需要被唤醒
    • PROPAGATE 释放共享资源时, 需要通知其他节点

上一篇文章 有提到, 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 操作, 这一点在上一篇文章 中也有提到。

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 操作去判断获取锁的结果。

转载地址:http://jgpdz.baihongyu.com/

你可能感兴趣的文章
访问servlet时弹出文件下载框解决方法
查看>>
IDEA-@Slf4j和log标签&@Data(Lombok)无效
查看>>
SpringCloud-Eureka报错 Error creating bean with name解决
查看>>
Thymeleaf 生成下标,索引,使用Stat变量
查看>>
全局变量初始化顺序的不确定性引发的bug
查看>>
ValueError: Unexpected end of file.
查看>>
六、登录(二)
查看>>
初始微服务---Springcloud发展【第一期】
查看>>
RAFT 拜占庭将军 共识算法
查看>>
UE4 错误列表 error码(只记录我遇到的情况,持续添加,未完成)
查看>>
could not initialize proxy - no Session (SSH)
查看>>
cmd编译.java文件 : java:720: 错误: 编码GBK的不可映射字符 Why ? ? ? ?
查看>>
【Jquery】获取当前窗口的宽度值/高度值
查看>>
Android 架构组件 – 让天下没有难做的 App
查看>>
启动MongoDB出现1053错误
查看>>
网络对抗技术-Exp2-后门原理与实践 20181314
查看>>
能解决数据可视化大屏需求的3款可视化工具
查看>>
欢迎来到小迪博客
查看>>
【Altium Designer21】工作栏中文解析
查看>>
[87]用secureCRT连接虚拟机中的Ubuntu系统,出现“远程主机拒绝连接”错误
查看>>