博客
关于我
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/

你可能感兴趣的文章
可变长度参数
查看>>
3、条件查询
查看>>
cordova打包apk更改图标
查看>>
GitHub上传时,项目在已有文档时直接push出现错误解决方案
查看>>
页面置换算法
查看>>
文件系统的层次结构
查看>>
减少磁盘延迟时间的方法
查看>>
vue(渐进式前端框架)
查看>>
权值初始化和与损失函数
查看>>
vscode设置eslint保存文件时自动修复eslint错误
查看>>
Remove Extra one 维护前缀最大最小值
查看>>
Gradle实战四:Jenkins持续集成
查看>>
wgcloud运维监控系统错误:防篡改校验错误次数大于10次,不再上报数据
查看>>
iOS 开发官方文档链接收集
查看>>
HDU - 4109 Instrction Arrangement
查看>>
JQuery--手风琴,留言板
查看>>
MFC 自定义消息发送字符串
查看>>
Linux操作系统的安装与使用
查看>>
C++ 继承 详解
查看>>
OSPF多区域
查看>>