ReentrantLock 源码分析

2023-10-26

ReentrantLock简单使用demo如下:

Lock lock = new ReentrantLock(); 
lock.lock();
try {
    //业务逻辑  
} finally {
  lock.unlock();
}

注:获取的锁代码要放到try块之外,防止获得锁代码异常,抛出异常的同时,也会导致一次锁的释放。释放代码一定要放到finally块中。

AQS
了解java中的锁,首先的了解AQS。
AQS(AbstractQueuedSynchronizer)队列同步器。是用来构建锁或者其它同步组件的基础框架,他实现了一个int成员变量标识同步状态(更改这个变量值来获取和释放锁),通过内置的FIFO双向队列来完成资源获取线程排队的工作。
AQS可以实现独占锁和共享锁,RenntrantLock实现的是独占锁,ReentrantReadWriteLock实现的是独占锁和共享锁,CountDownLatch实现的是共享锁。

ReentrantLock 类结构信息如下图:

Paste_Image.png

  • ReentrantLock 实现 Lock 和 Serializable 接口
  • RentrantLock 有三个内部类 Sync、NonfairSync 和 FairSync 类
  • Sync 继承 AbstractQueuedSynchronizer 抽象类
  • NonfairSync(非公平锁) 继承 Sync 抽象类
  • FairSync(公平锁) 继承 Sync 抽象类

公平锁和非公平锁

ReentrantLock 有两种实现方式,公平锁和非公平锁。

  • 公平锁:当前线程不立刻获得锁,而是先直进入等待队列中队尾进行排队获取锁。

  • 非公平锁:当前线程首先尝试获取一下锁(仅仅尝试一下),如果获取不到,则乖乖的进入到等待队列中去排队。

ReentrantLock实现公平锁和非公平锁代码如下:

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

* 获取非公平锁 *

   /**
    * Performs lock.  Try immediate barge, backing up to normal
    * acquire on failure.
    */
   final void lock() {
       if (compareAndSetState(0, 1))
           setExclusiveOwnerThread(Thread.currentThread());
       else
           acquire(1);
   }
  1. 首先通过CAS更新AQS中的state变量来获得锁(第一次获得锁),如果获取成功则把当前线程设置为独占锁
  2. 如果是设置失败,进入到acquire方法
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  1. 首先执行tryAcquire方法,尝试获得锁。
  2. 如果获取失败则进入addWaiter方法,构造同步节点,将该节点添加到同步队列尾部,并返回此节点,进入acquireQueued方法。
  3. acquireQueued方法,这个新节点死是循环的方式获取同步状态,如果获取不到则阻塞节点中的线程,阻塞后的节点等待前驱节点来唤醒。
   /**
    * Performs non-fair tryLock.  tryAcquire is implemented in
    * subclasses, but both need nonfair try for trylock method.
    */
   final boolean nonfairTryAcquire(int acquires) {
       final Thread current = Thread.currentThread();
       int c = getState();
       if (c == 0) {
           if (compareAndSetState(0, acquires)) {
               setExclusiveOwnerThread(current);
               return true;
           }
       }
       else if (current == getExclusiveOwnerThread()) {
           int nextc = c + acquires;
           if (nextc < 0) // overflow
               throw new Error("Maximum lock count exceeded");
           setState(nextc);
           return true;
       }
       return false;
   }

tryAcquire调用nonfairTryAcquire方法来第二次尝试获得锁
1. 如果state变量为0,则进行CAS尝试更新state来获得锁,并把该线程设置成独占锁,并返回true。
2. 如果state变量不为0,则判断当前线程是否为独占锁,如果是,则当前state+1(可重入锁),表示获取锁成功,更新state值,并返回true。这里更新state变量,不需要CAS更新,因为,当前线程已经获得锁。

   private Node addWaiter(Node mode) {
       Node node = new Node(Thread.currentThread(), mode);
       // Try the fast path of enq; backup to full enq on failure
       Node pred = tail;
       if (pred != null) {
           node.prev = pred;
           if (compareAndSetTail(pred, node)) {
               pred.next = node;
               return node;
           }
       }
       enq(node);
       return node;
   }

将构造的同步节点加入到同步队列中
1. 使用链表的方式把该Node节点添加到队列尾部,如果tail的前驱节点不为空(队列不为空),则进行CAS添加到队列尾部。
2. 如果更新失败(存在并发竞争更新),则进入enq方法进行添加

   private Node enq(final Node node) {
       for (;;) {
           Node t = tail;
           if (t == null) { // Must initialize
               if (compareAndSetHead(new Node()))
                   tail = head;
           } else {
               node.prev = t;
               if (compareAndSetTail(t, node)) {
                   t.next = node;
                   return t;
               }
           }
       }
   }

该方法使用CAS自旋的方式来保证向队列中添加Node(同步节点简写Node)
1. 如果队列为空,则把当前Node设置成头节点
2. 如果队列不为空,则向队列尾部添加Node

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在acquireQueued方法中,当前线程在死循环中尝试获取同步状态,
1. 如果当前节点的前驱节点头节点才能尝试获得锁,如果获得成功,则把当前线程设置成头结点,把之前的头结点从队列中移除,等待垃圾回收(没有对象引用)
2. 如果获取锁失败则进入shouldParkAfterFailedAcquire方法中检测当前节点是否可以被安全的挂起(阻塞),如果可以安全挂起则进入parkAndCheckInterrupt方法,把当前线程挂起,并检查刚线程是否执行了interrupted方法。

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 尝试将当前节点的前驱节点的等待状态设为SIGNAL
             * 1/这为什么用CAS,现在已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操作这个节点了,那为什么还要用CAS?而不直接赋值呢?
             * (解释:因为pred可以自己将自己的状态改为cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操作)
             * 2/既然前驱节点已经设为SIGNAL了,为什么最后还要返回false
             * (因为CAS可能会失败,这里不管失败与否,都返回false,下一次执行该方法的之后,pred的等待状态就是SIGNAL了)
             * (网上摘抄的,解释的很明白)
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

waitStatus状态值

状态 说明
CANCELLED 1 等待超时或者中断,需要从同步队列中取消
SIGNAL -1 后继节点出于等待状态,当前节点释放锁后将会唤醒后继节点
CONDITION -2 节点在等待队列中,节点线程等待在Condition上,其它线程对Condition调用signal()方法后,该节点将会从等待同步队列中移到同步队列中,然后等待获取锁。
PROPAGATE -3 表示下一次共享式同步状态获取将会无条件地传播下去
INITIAL 0 初始状态
  1. 首先获取前驱节点的等待状态ws
  2. 如果ws为SIGNAL则表示可以被前驱节点唤醒,当前线程就可以挂起,等待前驱节点唤醒,返回true(可以挂起)
  3. 如果ws>0说明,前驱节点取消了,并循环查找此前驱节点之前所有连续取消的节点。并返回false(不能挂起)。
  4. 尝试将当前节点的前驱节点的等待状态设为SIGNAL
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

把当前线程挂起,并检查刚线程是否执行了interrupted方法,并返回true、false。

公平锁

公平锁和非公平锁实现方式是一样的,唯一不同的是tryAcquire方法的实现,下面是公平锁tryAcquire方法实现:

   protected final boolean tryAcquire(int acquires) {
       final Thread current = Thread.currentThread();
       int c = getState();
       if (c == 0) {
           if (!hasQueuedPredecessors() &&
               compareAndSetState(0, acquires)) {
               setExclusiveOwnerThread(current);
               return true;
           }
       }
       else if (current == getExclusiveOwnerThread()) {
           int nextc = c + acquires;
           if (nextc < 0)
               throw new Error("Maximum lock count exceeded");
           setState(nextc);
           return true;
       }
       return false;
   }
  1. 首先获取当前锁状态,如果当前state==0(无锁),则进行获取锁操作
  2. hasQueuedPredecessors方法判断头结点是否当前线程,如果是当前线程则进行CAS更新获得锁,获取成功,把当前线程设置成独占锁。
  3. 如果不是头结点或获取锁失败则,则判断当前线程是否为独占锁,如果是,则当前state+1(可重入锁),表示获取锁成功,更新state值,并返回true。这里更新state变量,不需要CAS更新,因为,当前线程已经获得锁。

        本人简书blog地址:http://www.jianshu.com/u/1f0067e24ff8
        点击这里快速进入简书

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ReentrantLock 源码分析 的相关文章

随机推荐

  • Vim/Vi中保存文件并退出编辑器

    Vim Vi模式 启动Vim编辑器时 处于正常模式 在这种模式下 可以使用vim命令并浏览文件 0 打开package json 终端命令vim package json 1 进入编辑模式 按 i键 2 按 Esc可返回正常模式 3 打开文
  • 如何给数据库中的表插入数据?

    R星校长 为表的所有字段插入数据 向表中插入数据最简单的方法就是使用INSERT语句 INSERT语句需要你声明要插入内容的表 table 名和内容 values 语法规则为 INSERT INTO 表名 字段名 VALUES 内容 举个例
  • uni-app引入web3在真机运行下兼容性处理方法

    uni app开发跨平台应用程序 项目搭建主要前端框是Uni app Vue3 TS Vite 项目搭建参考文章Uni app Vue3 TS Vite 创建项目 Hbuilderx版本是3 6 17 安装web3 yarn add web
  • WebRTC音频系统 peerconnection初始化

    文章目录 2 1 peerconnection conductor 2 2 PeerConnectionFactory和PeerConnection 2 2 1 CreatePeerConnectionFactory 2 2 2 PeerC
  • vue之tab栏切换

    一个简单的tab栏切换组件 由tabs以及tab pane组成 效果图 使用
  • 物联网LoRa系列-32:LoRaWAN无线智能水表如何进行水量数据采集?脉冲采集、双干簧管、磁性元件、光电转换、霍尔元件

    目录 1 无线远传智能水表概述 2 常用智能水表的发展与类型 3 智能水表数据的采集的基本原理 3 1 光电转换型原理 3 2 双干簧管数据采集的基本原理 3 3 霍尔元件型原理 4 干簧管脉冲检测的基本原理 4 1 干簧管概述 干簧管是一
  • 一种比较省内存的稀疏矩阵Python存储方案

    原文 http www pythontab com html 2014 pythonhexinbiancheng 0522 788 html 推荐系统中经常需要处理类似user id item id rating这样的数据 其实就是数学里面
  • js的数据交互

    JSP 几个内置对象 out request response session 一 application 1 介绍 application 他是代表的整个项目的全局对象 application 所有项目下面的页面共享 所有的浏览器用户 访
  • 【问题解决】VSCode ESLint 保存格式化时不生效

    问题描述 VSCode 中安装了 Prettier 和 ESLint 插件 在项目中使用了 eslintrc js 文件规范项目代码 但是保存代码时格式化并不是按该文件的配置而是按 Prettier 的配置 问题解决 修改 Settings
  • 力扣的题库突然打不开的解决办法

    问题 力扣的题目打开很慢 然后题目那是灰色 解决方案 换个浏览器 清楚浏览器缓存 如果上述方法都不可以 检查一下自己有没有安装 脚本 如果有的话可以停用他 在浏览器右上角 有管理扩展的地方 停用一下油猴这个扩展 我下面的是已经删除之后的图
  • batocera 完整包_Batocera Plus 可能是现如今最强最全最好用的模拟器集成系统!

    Btocera PLUS系统开机界面 相信玩Batocera的圈子里面Batocera Plus 绝对是青出于蓝而胜于蓝的超级强劲改版 我们来细说为什么Batocera Plus如何受众人爱戴 甚至超过了原版的Batocera 自batoc
  • libgcrypt-1.10.2编程-计算摘要

    文章目录 使用libgcrypt计算摘要 一 计算少量数据摘要 二 计算大量数据摘要 在main函数中调用 使用libgcrypt计算摘要 libgcrypt编程计算摘要的方式有2种 一种可用来计算小数据的摘要 如字符串 一种可用来计算大数
  • edl后变暗后的解决方法

    由于edl类似于ssao 会变暗 这时候调整亮度时 可以类似于hdr 高动态范围转LDR 只是不色调映射就可以了 rgb各分量同时提高同一个比例即可 这样既可以变亮且不溢出 也可以不改变颜色 当然 可能会出现类似于深绿变浅绿的现象 但是视觉
  • 基于CH552G主控的开源九键小键盘(资料齐全)

    Content 1 前言 2 CH55X Arduino平台环境搭建 2 1 常规安装 2 2 HFS挂载本地服务器安装 3 例程使用 3 1 工程下载及编译 3 2 程序烧录 1 前言 芯片选型 CH552G 本文主要解决Arduino下
  • JSZIP+StreamSaver下载大文件打包遇到的问题

    背景 公司项目为了节约服务器硬盘 需要从微信企业微盘下载大批量文件 然后 进行客户端打包 问题 根据StreamSaver的代码演示例子 是通过指定Blob类型进行流下载更新 我尝试了这个方法 发现一旦Blob类型大于1G的时候 Chrom
  • DC-DC与电感

    作者 AirCity 2020 2 16 Aircity007 sina com 本文所有权归作者Aircity所有 DC DC即直流 直流转换器 输入是直流电压 输出也是直流电压 DC DC有升压型boost 降压型buck和升降压型bu
  • 基于SSM的海外代购系统设计与实现

    摘 要 近年来 我国的互联网方面发展十分的迅速 短短20年 信息时代就已经展现在了我们的面前 电商 是信息时代中出现的一大产物 电商的发展在很大程度上来说的的确确的方便了我们日常的部分的生活购物行为 从1997年电商的萌芽开始发展直至今日
  • 类的公有类型(public)和私有类型(private)

    数据成员可以为公有 函数成员也可以为私有 如果数据成员为公有 那么在对象的作用域内的任何函数都可以通过对象名使用该数据成员 如果函数成员为公有 那么该函数成员只能被公有的函数通过对象名调用
  • python二元函数图像在线绘制_“绘图,让科学生动起来”:Python-matplotlib绘图(多元函数拟合-3D绘图专题)...

    作者简介 85后一名分析化学工作者及科学爱好者 深爱着北京的老北京人 爱好运动 科学 旅行 个人微信公众号 科学是什么东东 欢迎关注 搭建python matplotlib绘图平台的四个要素是什么 它们是 python软件主体 pip库管理
  • ReentrantLock 源码分析

    ReentrantLock简单使用demo如下 Lock lock new ReentrantLock lock lock try 业务逻辑 finally lock unlock 注 获取的锁代码要放到try块之外 防止获得锁代码异常 抛