ReentrantLock的实现

2023-11-06

ReentrantLock可重入锁

我们可以利用这个实现对某一个操作约束为同有个时刻只能有一个线程能够操作。我们呢先看一下下面这个demo


public class ReentrantLockTest {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        Thread start1 = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("线程1执行任务");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程1执行任务结束");

                } finally {
                    lock.unlock();
                }


            }
        }, "t1");
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("线程2执行任务");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程2执行任务结束");

                } finally {
                    lock.unlock();
                }


            }
        }, "t2");

        start1.start();
        thread.start();


    }
}

加锁了之后就能够实现只能有一个线程获取执行任务结束另一个线程才能够执行这个任务。
在这里插入图片描述

我们看一下lock方法的源码

public void lock() {
        sync.lock();
    }

sync是ReentrantLock类里面的一个成员变量,final修饰的,它的类型就是Sync类型,Sync是ReentrantLock里面的一个静态内部抽象类,继承了AbstractQueuedSynchronizerSync类有两个实现类,分别是FairSyncNonfairSync,它们俩个都是ReentrantLock的静态内部类,都是被final修饰的,不能被继承。分别实现了公平锁和非公平锁。

而这个成员变量sync在调用ReentrantLock的构造方法的时候就会被赋值,

  • 无参构造
public ReentrantLock() {
        sync = new NonfairSync();
    }
  • 有参构造
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

非公平锁的实现

NonfairSync继承Sync,而Sync又继承了AbstractQueuedSynchronizer,而AbstractQueuedSynchronizer就是我们常说的AQS,抽象队列同步器,里面有一个state变量,这里不再赘述AbstractQueuedSynchronizer
我们呢直接看非公平锁中的lock方法

final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

首先利用哦cas操作如果成功就调用setExclusiveOwnerThread把获取到这个锁的线程设置为当前线程,否则调用acquire方法加入到等待锁的队列当中。这是AQS里面的方法。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这里首先调用tryAcquire在非公平锁里面的实现

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
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;
        }

获取锁成功分为两种情况,第一个if判断AQS的state是否等于0,表示锁没有人占有。接着,没有的话调用compareAndSetState使用CAS(Unsafe类调用本地方法)的方式修改state,传入的acquires写死是1。最后线程获取锁成功,setExclusiveOwnerThread将线程记录为独占锁的线程。

第二个if判断当前线程是否为独占锁的线程,因为ReentrantLock是可重入的,线程可以不停地lock来增加state的值,对应地需要unlock来解锁,直到state为零。

这里我们可以总结一下大致的流程是先尝试获取锁然后获取不到之后会在tryAcquire方法里面再次尝试获取到锁,这就是非公平所,直接尝试获取锁,不直接进入队列。如果还是不行,就会调用addWaiter方法。

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;
    }

这个方法首先呢其实就是将这个线程添加到等待队列中去,然后把尾节点设置为新添到这个队列的线程。具体分析如下:
如果第一次进来的话等待队列肯定是空的,所以这里的pred也就是tail就是null,进入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;
                }
            }
        }
    }

这里第一次循环会创建一个节点了,里面的线程属性是null,然后第二次循环就会将当前节点加入到这个头节点的下一个节点,compareAndSetHead方法和compareAndSetTail都调用了unsafe类的方法。
我们这里可以看到在addWaiter方法的外面还包裹了一个方法acquireQueued,这个方法是干嘛的呢?

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);
        }
    }

这个方法首先获取这个新加的这个节点的前面一个节点,如果前面这个节点是头节点,就会循环获取锁,知道获取成功。但是也有可能不是头节点,那如果不是头节点,就会走下面的逻辑shouldParkAfterFailedAcquire,在这个方法中呢会将前面这个节点的,一开始ws也就是节点的等待状态是0,所有会走下面的compareAndSetWaitStatus(pred, ws, Node.SIGNAL);将节点的等待状态设置称-1,为什么呢?这个等待状态的作用就是用来判断是否是最后一个节点,如果是最后一个节点,它的状态就是0,不是的话就是-1,这样就可以知道在这个节点的线程执行结束之后是否应该唤醒下一个节点的线程进行运行。如果节点的等待状态大于零,那么这个就是失效的节点,就会利用循环操作,将当前按节点是上一个节点的指向指向成上一个的上一个,直到找到合适的节点。

但是我们上面的代码中还有一个faiedl的判断,然后如果正常执行,finally里面的代码块是一定不会执行的,只有parkAndCheckInterrupt()方法中,jvm内部出现了异常,才有可能执行finally里面的方法。

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 {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

上面的方法shouldParkAfterFailedAcquire在第一次循环中被调用的时候当然是false,但是如果第二次循环还是没有获取到线程就会返回ture,然后就会调用下面的parkAndCheckInterrupt()方法,这个方法就会将当前线程进行park,interrupted是Thread类的静态方法,返回这个线程的中断状态,并将当前线程的中断状态设置称false。

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

公平锁的实现

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

与nonfairAcquire()方法相比,唯一的不同之处在于FairSync多了hasQueuedPredecessors()(由AQS提供)方法:

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

其作用是判断:是否存在比当前线程等待锁时间更长的线程,有就返回true,没有就返回false。

也就是说每次获取锁都需要判断队列中是否还有排在前面的线程,若不存在才能继续获取锁!!若队列还存在等待线程则获取失败,当前线程进入队列尾部。这里有集中情况是需要加入到队列中的。

  • h != t:这种就是队列中有元素在等待了。
  • (s = h.next) == null:这种就是在并发的情况下,一个线程还没有来得及将头节点和尾节点同时指向头节点的时候就会存在这种情况。
  • s.thread != Thread.currentThread():这种情况就是避免当前线程已经在等待了,所以没有必要再次等待了。

ReentrantLock解锁方法的实现

调用unlock方法,就会调用sync里面的release方法

public void unlock() {
        sync.release(1);
    }
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方法中就会尝试释放锁,这里如果锁被持有,就会返回false,知道没有线程占有锁的时候才会返回true。一旦这个返回true,就会调用unparkSuccessor方法。

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

unparkSuccessor方法中,就会从后面往前遍历链表中的节点,找到需要被唤醒的节点然后唤醒节点中park的线程。

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ReentrantLock的实现 的相关文章

  • 枚举的子类化

    有没有一种简单的方法来子类化Javaenum 我问这个问题是因为我有大约 10 个实现相同接口的对象 但它们对某些方法也有相同的实现 因此我想通过将所有相同的实现放置在扩展的中间对象中来重用代码Enum它也是我需要的所有其他类的超类 或许事
  • Jackson JSON + Java 泛型

    我正在尝试将以下 JSON 反序列化 映射到List
  • Android:如何暂停和恢复可运行线程?

    我正在使用 postDelayed 可运行线程 当我按下按钮时 我需要暂停并恢复该线程 请任何人帮助我 这是我的主题 protected void animation music6 music4 postDelayed new Runnab
  • 连接外部 Accumulo 实例和 java

    我正在尝试使用 Accumulo 连接到虚拟机 问题是 我无法将其连接到 Java 中 我可以看到 Apache 抛出的网页 但我无法让它与代码一起工作 我认为这是缺乏知识的问题而不是真正的问题 但我找不到这方面的文档 所有示例都使用 lo
  • 非易失性领域的出版与阅读

    public class Factory private Singleton instance public Singleton getInstance Singleton res instance if res null synchron
  • 如何以编程方式使用包含多列的 where-in 子句执行 PostgreSQL 查询?

    我的查询是这样的 select from plat customs complex where code t code s in 01013090 10 01029010 90 它在 psql 控制台中运行良好 我的问题是如何在客户端代码中
  • 有人用过 ServiceLoader 和 Guice 一起使用吗?

    我一直想通过我们的应用程序 构建系统进行更大规模的尝试 但更高的优先级不断将其推到次要地位 这似乎是加载 Guice 模块的好方法 并且避免了关于 硬编码配置 的常见抱怨 单个配置属性很少会自行更改 但您几乎总是会有一组配置文件 通常用于不
  • 什么是内部类的合成反向引用

    我正在寻找应用程序中的内存泄漏 我正在使用的探查器告诉我寻找这些类型的引用 但我不知道我在寻找什么 有人可以解释一下吗 Thanks Elliott 您可以对 OUTER 类进行合成反向引用 但不能对内部类实例进行合成 e g class
  • 无法使用 datastax java 驱动程序通过 UDT 密钥从 cassandra 检索

    我正在尝试使用用户定义的类型作为分区键将对象存储在 cassandra 中 我正在使用 datastax java 驱动程序进行对象映射 虽然我能够插入到数据库中 但无法检索该对象 如果我更改分区键以使用非 udt 例如文本 我就能够保存和
  • 在 Spring 中为 @Pathvariable 添加类级别验证

    在发布这个问题之前 我已经做了很多研究并尝试了很多可用的解决方案 这是我陷入的棘手情况 我有一个 Spring 控制器 它有多个请求映射 它们都有 PathVariables 控制器如下所示 Controller EnableWebMvc
  • 2^31 次方的 Java 指数错误 [重复]

    这个问题在这里已经有答案了 我正在编写一个java程序来输出2的指数幂 顺便说一句 我不能使用Math pow 但是在 2 31 和 2 32 处我得到了其他东西 另外 我不打算接受负整数 My code class PrintPowers
  • IntelliJ Idea:将简单的 Java servlet(无 JSP)部署到 Tomcat 7

    我尝试按照教程进行操作here http wiki jetbrains net intellij Creating a simple Web application and deploying it to Tomcat部署 servlet
  • 数据库中的持久日期不等于检索日期

    我有一个具有 Date 属性的简单实体类 此属性对应于 MySQL 日期时间列 Entity public class Entity Column name start date Temporal TemporalType TIMESTAM
  • 按降序排序映射java8 [重复]

    这个问题在这里已经有答案了 private static
  • 我们如何使用 thymeleaf 绑定对象列表的列表

    我有一个表单 用户可以在其中添加任意数量的内容表对象这也可以包含他想要的列对象 就像在 SQL 中构建表一样 我尝试了下面的代码 但没有任何效果 并且当我尝试绑定两个列表时 表单不再出现 控制器 ModelAttribute page pu
  • titledBorder 标题中的图标

    您好 是否可以在 titledBorder 的标题中放置一个图标 例如以下代码 import java awt GridLayout import javax swing JFrame import javax swing JLabel i
  • 即使禁用安全性,OAuth 令牌 API 也无法在 Elastic Search 中工作

    我是 Elastic search 新手 使用 Elastic search 版本 7 7 1 我想通过以下方式生成 OAuth 令牌弹性搜索文档 https www elastic co guide en elasticsearch re
  • Android ScrollView,检查当前是否滚动

    有没有办法检查标准 ScrollView 当前是否正在滚动 方向是向上还是向下并不重要 我只需要检查它当前是否正在滚动 ScrollView当前形式不提供用于检测滚动事件的回调 有两种解决方法可用 1 Use a ListView并实施On
  • Java 的“&&”与“&”运算符

    我使用的示例来自 Java Herbert Schildt 的完整参考文献 第 12 版 Java 是 14 他给出了以下 2 个示例 如果阻止 第一个是好的 第二个是错误的 因此发表评论 public class PatternMatch
  • Spring 作为 JNDI 提供者?

    我想使用 Spring 作为 JNDI 提供程序 这意味着我想在 Spring 上下文中配置一个 bean 可以通过 JNDI 访问该 bean 这看起来像这样

随机推荐

  • 从char到QChar

    char类型是c c 中内置的类型 描述了1个字节的内存信息的解析 比如 char gemfield g 那么在由gemfield标记的这块内存的大小就是1个字节 信息就是01100111 8位 再比如 char gemfield 汉 那么
  • Java设计模式之观察者模式

    摘录 观察者模式是对象的行为模式 又叫发布 订阅 Publish Subscribe 模式 模型 视图 Model View 模式 源 监听器 Source Listener 模式或从属者 Dependents 模式 观察者模式定义了一种一
  • linux rfkill

    http blog csdn net eager7 article details 8121143
  • FFmpeg音视频播放器流程

    音视频播放器流程 ffmpeg解封装解码流程API ffmpeg官网 FFmpeg
  • [STM32]KEIL调试程序进入HardFault_Handler异常处理总结

    在做CORTEX M3单片机开发的时候 如STM32 可能会遇到设备跑着跑着程序死机的情况 往往调试起来很多时候发现是程序进入HardFault Handler系统异常 根据相关资料和M3权威指南是可以通过调试查找出程序的问题点和解决问题的
  • eclipse gradle打包_Spring Boot(十二):Spring Boot 如何测试打包部署

    部分面试资料链接 https pan baidu com s 1qDb2YoCopCHoQXH15jiLhA 密码 jsam 想获得全部面试必看资料 关注公众号 大家可以在公众号后台回复 知乎 即可 有很多网友会时不时的问我 Spring
  • 一个人的成功不是没有理由的!(人物之楼天城)

    昨天 杭州第十四中学请来毕业生楼天城 给全体学生做励志讲座 讲高中三年的学习生活和理科思维的培养 讲座前 老师介绍 楼天城同学2004年毕业于十四中 保送清华大学 博士毕业 是公认的计算机天才 公认的中国大学生编程竞赛第一人 常以一人单挑一
  • 利用python摘取文本中所需信息,并保存为txt格式

    项目所需 IC设计中难免会处理大量文本信息 我就在项目中遇到了 对于一个几万行的解码模块 提取出其中的指令 如果不用脚本将会很麻烦 下面我将一个小小的例子分享给大家 刚学python 如果有更方便的实现方法清多多指教 目的 1 在几万行解码
  • Git常用命令总结

    Git常用命令总结 git init 在本地新建一个repo 进入一个项目目录 执行git init 会初始化一个repo 并在当前文件夹下创建一个 git文件夹 git clone 获取一个url对应的远程Git repo 创建一个loc
  • openssl的证书链验证

    原文地址 http blog csdn net dog250 article details 5442914 使用openssl验证证书链可以用以下命令 debian home zhaoya openssl openssl verify C
  • C语言分支循环语句

    需提前看 初识C语言 5 C语言一些基本常识 目录 分支语句 if语句 单if语句使用 if else语句 if else if else语句 switch语句 switch基本结构 break作用 default作用 循环语句 while
  • 【Vscode

    Rmd文件转html R语言环境 Vscode扩展安装及配置 配置radian R依赖包 pandoc安装 配置pandoc环境变量 验证是否有效 转rmd为html 注意本文代码块均为R语言代码 在R语言环境下执行即可 R语言环境 官网中
  • shell I/O重定向

    shell重定向 lt 改变标准输入 program lt file 可将program 的标准输入改为file tr d r lt dos file txt 以 gt 改变标准输出 program gt file 可将program的标准
  • Qt基础之三十:百万级任务并发处理

    在实际的开发过程中 经常会遇到要处理大量任务场景 比如说压缩文件夹中的所有文件 对文件夹中的所有文件加密 上传文件夹中的所有文件到ftp等等 这里说百万级并不夸张 理论上文件夹中有任意多个文件都是可以的 本文以压缩文件夹中的100万张jpg
  • 三国志13pk版登录武将输入中文名方法与更改图像详解

    今天来个正经的文 三国志13里登录武将 设定姓名时 如果用的是自带输入法 就会出现一堆乱码 这时候 有两种解决方法 下载一个具有大五码的输入法 然后输入时候既要切换输入法 切换繁体 切换窗口模式 很麻烦 尤其在输入列传的时候 打很多字会很不
  • 【架构优化过程思考】技术方案评估的三个维度

    方案的选择决定了当下实现方案的资源投入及产出对产 也决定后续的成本 评估一个方案 首先要评估这个方案的有效性 也就是说要解决这个问题 实现目标 当前的这个方案是否足够的有效 还是在部分的场景下有效 如果是全部的有效那么该方案就不会出现上线之
  • 二叉树--合并二叉树

    问题 已知两颗二叉树 将它们合并成一颗二叉树 合并规则是 都存在的结点 就将结点值加起来 否则空的位置就由另一个树的结点来代替 思路 通过二叉树的前序遍历方法进行遍历 同时 t1二叉树作为蓝本进行计算 注意设置两个指针记录t1和t2遍历到的
  • JavaScript重写alert,confirm,prompt方法(JavaScript实现线程非阻塞式暂停和启动)

    得有段时间没好好写篇博客了 这次我们从一个题目开始吧 首先我给大家出一道题目 大家可以先思考一下 再往下看 题目是 请用JavaScript重写confirm方法 实现和confirm同样的功能 乍一看可能感觉很简单 定义一个confirm
  • php cms 自动分词,灵活运用PHPAnalysis分词组件,实现Phpcms v9关键词自动分词

    在2019年12月下旬 Phpcms官网phpcms cn关闭后 原有的分词api接口 http tool phpcms cn api get keywords php 已经失效 在录入标题后再也不能自动提取关键词到关键词的输入栏了 针对这
  • ReentrantLock的实现

    ReentrantLock可重入锁 我们可以利用这个实现对某一个操作约束为同有个时刻只能有一个线程能够操作 我们呢先看一下下面这个demo public class ReentrantLockTest public static void