Java锁之ReentrantLock(源码详解)

news2025/1/17 21:45:36
  • 视频地址
  • Java学习文档

ReentrantLock 这个Java中重要的锁,我想可能很多人只是听过,并没有使用过,我在看RocketMQ客户端源码的时候发现大量的使用了这个ReentrantLock,从而引起了我的兴趣,下面我们一起从源码的角度来学习ReentrantLock。

我们先来看一下ReentrantLock的继承关系

在这里插入图片描述

  1. 它实现了 Lock和Serializable接口
  2. 它有三个内部类 Sync、NonfairSync、FairSync
  3. 其中 Sync 是抽象内部类,NonfairSync、FairSync 都继承了 Sync

在正式学习源码之前,我们先来了解几个锁相关的理论

  1. 可重入锁:可重入锁的意思就是当一个线程拿到锁之后,它还可以继续加锁,而不会出现死锁,相对应的就是在释放锁的时候也要释放多次。
  2. CAS:比较并交换,这是乐观锁的一个常规实现,先对比旧的值是不是A,如果是就替换成B,如果不是就不替换。但是会出现ABA的问题,比如我们对比的时候是A,但是我们在替换的过程中,其它线程又去把A换成C,再换成A,这时候我们判断还是A就替换了,但实际上其实已经改变过了,但在大多数场景中这是没有问题了。
  3. 公平锁/非公平锁:当线程A去尝试获取锁,这时候没获取到就进入队列等待,然后B来获取锁,这时候刚好锁释放了,非公平锁代码B会直接获取到锁。但明明是A先来的呀?A却还在等待,这样会产生“饿死现象”——A可能永远也获取不到锁了。但这样的好处是不需要去激活等待的线程,当前有资源就直接用,性能更好。(不需要管等待队列的锁就是非公平锁,反之亦然

1、Sync

我们由内而外的来剖析这个 ReentrantLock,先来看看它内在的 儿子(Sync)、和孙子(NonFairSync、FairSync)

// 1.继承了 AbstractQueuedSynchronizer 
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    // 定义了一个抽象方法 lock,有子类去具体实现
    abstract void lock();

    // 非公平尝试获得锁实现
    final boolean nonfairTryAcquire(int acquires) { // ... }
    
    // 释放锁
    protected final boolean tryRelease(int releases) { // ...}
    
    // 判断当前锁持有者是不是自己
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
    
    // 创建一个条件
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // 获取当前持有锁的线程
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    
    // ReentrantLock 是可重入锁,获取当前加锁的次数
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }
    
    // 判断当前锁是否被已经被持有
    final boolean isLocked() {
        return getState() != 0;
    }

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

上面有2个重要的方法,我在上面给注释了,下面来详细解析这两个方法 nonfairTryAcquire、tryRelease


1、nonfairTryAcquire

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

非公平尝试获取锁,它是被 final修饰,所以不能被重写,但是可以被子类使用。

  • 它的逻辑也很简单,如果当前锁没有被使用,那就使用 CAS算法 来设置值,这里调用的是底层的方法,如果设置成功说明当前可以获得锁,把当前持有锁的线程设置成当前线程。
  • 如果当前锁已经被持有了,并且持有锁的线程就是当前线程,那就让持有次数累加(ReentrantLock是可重入锁)
  • 如果不满足上述两种就返回 false。

2、tryRelease

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

它是被 protected final 修饰的方法,也就是不可以被重写,而且只能自己和子类使用。


它的逻辑也很简单,如果当前操作线程和持有锁的不是同一个就抛出异常,如果减完之后加锁标识为 0,就说明已经彻底释放了,就把当前持有锁的线程设置为 null,最后返回释放结果。


2、NonFairSync

不公平锁,一个静态内部类,它里面就2个方法

  1. lock 方法重写,如果可以CAS成功,就直接让当前线程持有锁(典型的不公平实现),否则就调用 爷爷的acquire方法。(acquire 是 AbstractQueuedSynchronizer的方法)
  2. tryAcquire 获取锁,直接调用父类的不公平加锁实现。
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

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

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

1、acquire

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
  1. 首先调用tryAcquire 去尝试获得锁,上面我们看到非公平锁里面重写了 tryAcquire 方法,其实就是调用 Sync 的 nonfairTryAcquire 方法。
  2. 如果已经获取到锁,就结束了,如果获取不到就把当前请求加入到队列中去 acquireQueued
  3. 通知当前线程中断 selfInterrupt

3、FairSync

公平锁,一个静态内部类,它里面就2个方法

  1. Lock 直接调用爷爷的 acquire 方法,上面已经详解过了
  2. tryAcquire 尝试获得锁方法,它这里唯一不一样的就是如果当前锁没有被持有,在自己持有之前会先检查排队的队列是否有等到的,如果有则获取失败。
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;
    }
}

4、ReentrantLock

上面我们已经着重看了 ReentrantLock 的内部类了,现在我们来看看 ReentrantLock 本身。

在这里插入图片描述


1、内部参数

这两个参数就没什么好说了的

private static final long serialVersionUID = 7373984872572414699L;

private final Sync sync;

2、构造方法

  1. 默认的空参构造方法创建的是 非公平锁
  2. 有参构造方法,根据传递的参数确定锁是公平还是非公平
public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

3、普通方法


1、lock

直接调用 sync 的lock方法,公平锁和非公平锁都有自己的实现

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

2、lockInterruptibly

获取锁,如果当前线程中断就抛出异常

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

3、tryLock

以非公平的方式获取锁

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

4、tryLock(long timeout, TimeUnit unit)

根据当前锁的类型(公平 or 非公平),来获取锁,并设置超时时间

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

5、unlock

释放锁

public void unlock() {
    sync.release(1);
}

6、newCondition

创建一个条件

public Condition newCondition() {
    return sync.newCondition();
}

7、getHoldCount

获取当前可重入锁的加锁次数

public int getHoldCount() {
    return sync.getHoldCount();
}

final int getHoldCount() {
    return isHeldExclusively() ? getState() : 0;
}

8、isHeldByCurrentThread

判断当前持有锁的对象是不是自己

public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}

protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}

9、isLocked

看当前锁,是否已经被持有了

public boolean isLocked() {
    return sync.isLocked();
}

final boolean isLocked() {
    return getState() != 0;
}

10、isFair

判断当前锁是否是公平锁

public final boolean isFair() {
    return sync instanceof FairSync;
}

11、getOwner

获取当前持有锁的线程

protected Thread getOwner() {
    return sync.getOwner();
}

12、hasQueuedThreads

判断当前是否有等待锁的

public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads();
}

// AbstractQueuedSynchronizer 的方法
public final boolean hasQueuedThreads() {
    return head != tail;
}

13、getQueueLength

获取等待锁的长度

public final int getQueueLength() {
    return sync.getQueueLength();
}

// AbstractQueuedSynchronizer 的方法
public final int getQueueLength() {
    int n = 0;
    for (Node p = tail; p != null; p = p.prev) {
        if (p.thread != null)
            ++n;
    }
    return n;
}

14、getQueuedThreads

获取正在等待锁的线程

protected Collection<Thread> getQueuedThreads() {
    return sync.getQueuedThreads();
}

// AbstractQueuedSynchronizer 的方法
public final Collection<Thread> getQueuedThreads() {
    ArrayList<Thread> list = new ArrayList<Thread>();
    for (Node p = tail; p != null; p = p.prev) {
        Thread t = p.thread;
        if (t != null)
            list.add(t);
    }
    return list;
}

15、hasWaiters

是否有等待某种添加的锁

public boolean hasWaiters(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

16、getWaitQueueLength

获取等待某种条件锁的数量

public int getWaitQueueLength(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}

17、getWaitingThreads

获取等待某种条件锁的线程

protected Collection<Thread> getWaitingThreads(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}

18、toString

public String toString() {
    Thread o = sync.getOwner();
    return super.toString() + ((o == null) ?
                               "[Unlocked]" :
                               "[Locked by thread " + o.getName() + "]");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158682.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JDK7时间相关类超详细总结(含多个实例)

JDK7时间相关类一、概述二、Date类1.构造函数2.常用函数1️⃣格式2️⃣实例三、 SimpleDateFormat类1.概述2.构造方法3.常用方法1️⃣格式2️⃣实例四、Calendar类1.概述2.使用方法3.常用方法4.实例五、结语一、概述 本文主要介绍JDK7中的时间相关类 二、Date类 1.构造函数 …

JSP SSM评估文档管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 JSPSSM评估文档管理系统 是一套完善的系统源码&#xff0c;对理解JSP java SrpingMVC mybiats 框架 MVC编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;以及相应配套的设计文档 &#xff0c;系统主要采用B/S模式开发。 研究的基本…

从零备战蓝桥杯——动态规划(子序列篇)

文章目录啥也别说了直接进入正题&#xff1a;不连续子序列问题300. 最长递增子序列1143. 最长公共子序列1035. 不相交的线连续子序列问题674. 最长连续递增序列718. 最长重复子数组53. 最大子数组和编辑距离问题392. 判断子序列困难题&#xff1a;115. 不同的子序列583. 两个字…

使用 ORM 方式查询 Mongodb 里的数据,再也不用记 Mongodb 的语法(ORM Bee)

使用ORM方式查询Mongodb里的数据,再也不用记Mongodb的语法&#xff08;ORM Bee) Mongodb的语法可读性差&#xff0c;要写复杂查询&#xff0c;要求技术能力高&#xff1b;Java驱动&#xff0c;还要使用另一种语法&#xff1b;学习成本太高了。 可以使用ORM方式&#xff0c;轻松…

数字IC设计、验证、FPGA笔试必会 - Verilog经典习题 (四)移位运算与乘法

数字IC设计、验证、FPGA笔试必会 - Verilog经典习题 &#xff08;四&#xff09;移位运算与乘法 &#x1f508;声明&#xff1a; &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN博客 &#x1f9e8;未经作者允许&#xff0c;禁止转载 &#x1f511;系列专栏&#xff1a;牛客Ve…

C++:函数对象:Lambda:Lambda详解(三)

1&#xff1a;定义 lambda表达式就是一个函数&#xff08;匿名函数&#xff09;&#xff0c;也就是一个没有函数名的函数。为什么不需要函数名了&#xff1f; 因为我们直接&#xff08;一次性的&#xff09;用它&#xff0c;不需要其他地方调用它。lambda表达式也叫闭包&#x…

K8s部署前后端分离项目(二)

K8s容器部署两个项目&#xff08;主节点操作&#xff09; 1、创建yaml文件 master节点上创建todo-list.yaml文件&#xff0c;两个jar包设置了两个副本&#xff0c;vue设置了一个副本&#xff0c;端口号固定配置。 当前文件目录为&#xff1a;/home/docker 创建yaml文件命令为&…

数学建模----图与网络模型

目录 一.图的基本概念与数据结构 1.基本概念 2.图与网络的数据结构 1.邻接矩阵表示法 2.关联矩阵 3.Matlab工具箱简介 1.图的生成 4.问题讨论 1.最短路问题 2.最小生成树问题 一.图的基本概念与数据结构 1.基本概念 点对应于研究对象&#xff0c;根据关系将一些点对应相…

第14章 多数据库支持之MySql

对于一个工程性程序而言&#xff0c;是否支持对多数据库的CURD操作&#xff0c;不是必须的&#xff0c;只是为了预防程序部署和运行过程中如果因为版权问题而产生的额外成本而预留的退路。对于移动设备的前后端分离程序而言&#xff0c;如果数据库需要部署到移动设备中&#xf…

【UE4 第一人称射击游戏】42-消耗和重新装载“M4A1”弹药

上一篇&#xff1a;【UE4 第一人称射击游戏】41-让“M4A1”拥有独立的弹药系统本篇效果&#xff1a;可以看到当玩家拾取了“M4A1”后&#xff0c;实现了重新装弹的功能&#xff0c;与“AK47”的弹药系统是相互独立的。步骤&#xff1a;打开“ThirdPersonCharacter”&#xff0c…

PHP session反序列化漏洞原理解析

什么是session 官方Session定义&#xff1a;在计算机中&#xff0c;尤其是在网络应用中&#xff0c;称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。主要有以下特点&#xff1a; session保存的位置是在服务器端 session通常是要配合cookie使用 因为HTTP…

Docker Swarm NFS 数据持久化存储

目录一、Swarm 集群部署二、NFS 服务部署三、Swarm 使用 NFS3.1 通过 Volume3.1.1 创建 Volume3.1.2 使用 Volume3.1.3 验证数据共享特性3.2 通过 Docker Stack3.2.1 创建 YAML 文件3.2.2 使用 YAML 文件3.2.3 验证数据共享特性一、Swarm 集群部署 可参考我前面的博客《基于 L…

从数据到价值,DataOps精益数据运营概述

作者&#xff1a;陈荣耀 阿里云全球技术服务团队 一、背景&挑战 数字化时代&#xff0c;企业希望借助数字化的技术能力来提升企业的经营能力&#xff0c;从最终业务目标上来看&#xff0c;一般分三类&#xff1a; 1. 增加收入&#xff1a;基于经营数据的智能分析来提升产…

24、TORCH.UTILS.DATA

PyTorch 数据加载实用程序的核心是 torch.utils.data.DataLoader 类。它代表一个可在数据集上迭代的 Python&#xff0c;支持map-style and iterable-style datasets,customizing data loading order,automatic batching,single- and multi-process data loading,automatic mem…

CTK Plugin Framework插件框架学习--服务追踪

文章目录一、前言二、新建插件PluginA三、新建插件PluginB四、测试一、前言 服务追踪&#xff1a;如果想在B插件里使用A服务&#xff0c;可以专门写一个类继承ctkServiceTracker&#xff0c;在这个类里完成对A服务的底层操作&#xff0c;然后在B插件里通过这个类提供的接口来使…

重庆市市长胡衡华会见深兰科技董事长陈海波一行

1月9日&#xff0c;重庆市市长胡衡华会见了赴渝考察调研的深兰科技集团创始人、董事长陈海波一行&#xff0c;双方就开展互利合作进行了深入交流。胡衡华市长会见深兰科技考察团重庆市委常委、副市长陈鸣波&#xff0c;市政府秘书长、办公厅主任欧顺清&#xff0c;市政府副秘书…

ResT: An Efficient Transformer for Visual Recognition

文章地址: https://arxiv.org/pdf/2105.13677.pdf codeResT: An Efficient Transformer for Visual Recognition一、引言二、ResT一、Transformer模块的再思考二、Efficient Transformer Block三、Patch Embedding四、Positional Encoding五、整体架构三、实验一、分类二、目标…

go import package 入门lib1 is not in GOROOT

main.go:4:2: package lib1 is not in GOROOT (/usr/local/go/src/lib1)├── 5-init│ ├── lib1│ │ └── lib1.go│ └── lib2│ └── lib2.go├── const.go├── firstVar.go├── go.mod├── helloGolang.go├── main.go└── test3function.gogo env …

Java学习之单例设计模式

目录 设计模式 单例模式 一、饿汉式 二、懒汉式 三、饿汉式VS懒汉式 总结 设计模式 1.静态方法和属性的经典使用 2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。就像是经典的棋谱&#xff0c;不同的棋局&#xff0c;我们用…

连接查询之外连接(左外链接和右外连接)

内连接&#xff1a; 假设A表和B表进行连接查询&#xff0c;使用内连接的话&#xff0c;凡是A表和B表能够匹配上的记录被查询出来&#xff0c;这就是内连接。A、B两张表没有主副之分&#xff0c;两张表是平等的。 外连接&#xff1a; 假设A表和B表进行连接查询&#xff0c;使用…