【JUC系列】ReentrantLock实现本地锁的源码分析

news2024/11/14 2:07:46

使用场景

public class ReentrantLockTest {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(()->{
            lock.lock();
            // do something
            System.out.println("111");
            try {
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }).start();

        new Thread(()->{
            lock.lock();
            // do something
            try {
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("222");
            lock.unlock();
        }).start();
    }
}

在这里插入图片描述

源码分析

默认使用非公平锁。

    public ReentrantLock() {
        sync = new NonfairSync();
    }

加锁

ReentrantLock#lock加锁,使用sync进行加锁。

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

如果当前的状态为0,则设置exclusiveOwnerThread = thread;,表示当前线程占住当前锁对象。

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

AbstractQueuedSynchronizer#acquire

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

非公平锁的实现,ReentrantLock.NonfairSync#tryAcquire

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

ReentrantLock.Sync#nonfairTryAcquire。当前状态值为0,直接获取锁,如果锁是当前线程占用,则是可重复锁,state++。

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

如果没有获取到锁,在队列中等待,执行AbstractQueuedSynchronizer#addWaiter。末尾节点不为空,设置末尾节点为node的前置节点,node为新的末尾结点。

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

末尾节点为空,执行AbstractQueuedSynchronizer#enq。进行末尾节点的初始化,再插入node节点。

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

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

AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire,前置节点可唤醒,返回true,否则设置前置节点的等待状态为-1。

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

AbstractQueuedSynchronizer#parkAndCheckInterrupt,找到唤醒自己的,进入休眠。

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

解锁

ReentrantLock#unlock

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

AbstractQueuedSynchronizer#release。如果成功释放锁,且头节点不为空,waitStatus不为0,执行AbstractQueuedSynchronizer#unparkSuccessor

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

ReentrantLock.Sync#tryRelease,修改state-1,如果state=0,,则释放exclusiveOwnerThread,返回true,否则返回false。

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

AbstractQueuedSynchronizer#unparkSuccessor。从尾结点找到waitStatus<=0的节点,唤醒

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

公平锁和非公平锁的不同在于tryAcquire方法。核心在于AbstractQueuedSynchronizer#hasQueuedPredecessors,该方法判断锁同步队列没有多余的节点。

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变成1,设置exclusiveOwnerThread = thread;
    • 有锁,且拥有锁的线程和加锁线程是同一个,state++;
    • 有锁,但拥有锁的线程和加锁线程不是用一个,队尾添加节点,设置前置节点的waitStatus=-1,进行休眠。
  2. 解锁的逻辑。state–,当state=0,释放锁成功,设置exclusiveOwnerThread = null;,唤醒最后一个waitStatus<=0

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

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

相关文章

[ 华为云 ] 云计算中Region、VPC、AZ 是什么,他们又是什么关系,应该如何抉择

前几天看到一个问答帖&#xff0c;我回答完了才发现这个帖子居然是去年的也没人回复&#xff0c;其中他问了一些华为云的问题&#xff0c;对于其中的一些概念&#xff0c;这里来总结讲解一下&#xff0c;希望对学习华为云的小伙伴有所帮助。 文章目录区域&#xff08;Region&am…

致 Tapdata 开源贡献者:聊聊 2022 年的进展和新一年的共建计划

岁末年初&#xff0c;在开源领域刚埋下一颗生机勃勃的种子的 Tapdata&#xff0c;想和正在关注我们的开发者&#xff0c;聊聊这一年的进展和新一年的共建计划。 2022年4月&#xff0c;Tapdata 宣布开源 PDK&#xff08;Plugin Development Kit&#xff09;&#xff0c;将自身的…

前端js实现文件多次添加累加上传和选择删除(django+js)- 添加累加文件上传 (一)

前言 原本的多文件上传功能在选择文件时&#xff0c;只能通过同一范围的鼠标框选或者ctrl/shift多选取选择文件&#xff0c;这样选择文件很不灵活&#xff0c;而且在确定之后如果漏选了文件&#xff0c;再次点击上传按钮时会清空表单里的文件信息&#xff0c;只能重复之前的操…

数据库mysql调优

问题描述: mysql dba在mysql服务端启用了连接在空闲一定时间 (10分钟) 后&#xff0c;就自动关闭连接(连接失效)的功能&#xff0c;导致java端连接池在空闲一段时间后&#xff0c;连接被自动关闭(自动失效)。为了避免这种情况出现&#xff0c;可以在dbcp上配置空闲的时候检测连…

PCI、PCI-X、PCI-E、PCI-E Card、Mini PCI-E、M.2、Add-in Card 它们有啥区别?这些概念你搞清楚了吗?

搞硬件或通信的“攻城狮”们&#xff0c;免不了要和各种通信协议及接口打交道。比如&#xff0c;我们经常接触PCI、PCI-X、PCI-E、PCI-E Card、Mini PCI-E、M.2(NGFF)、Add-in Card这些概念&#xff0c;作为“攻城狮”队伍中的一员&#xff0c;你搞清楚它们之间的关系了吗&…

Python爬虫教你爬取csdn作者排行榜

(一)两种爬取方式介绍 1.自动化测试工具 安装好驱动(以前的selenium文章有教程),然后进行元素定位&#xff0c;最后数据提取&#xff0c;用xls表格进行持久化存储 2.requests库 利用基本方法发起请求&#xff0c;获得json数据进行持久化存储 本篇文章先讲解第二种&#xf…

洛谷——树与图dp与状压dp

文章目录[NOIP1996 提高组] 挖地雷题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路代码最大食物链计数题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路代码[ZJOI2006]三色二叉树题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #…

浅说 INSERT/块参照/BlockReference

在AutoCAD中使用INSERT命令插入一个块时&#xff0c;生成块参照实体&#xff0c;也叫INSERT实体&#xff0c;在ObjectARX中&#xff0c;对应类AcDbBlockReference。反正就这么一个东西&#xff0c;名儿还挺多&#xff0c;我家乡有句话叫“二嫚她婆婆俩名”&#xff0c;莫非这是…

数据结构(5) treap

活动 - AcWing 参考—《算法竞赛进阶指南》-lyd 目录 一、概述 二、具体操作详解 1.常见操作 2.结构定义 3.操作基础函数 &#xff08;1&#xff09;pushup &#xff08;2&#xff09; 获得一个新节点 &#xff08;3&#xff09;左旋右旋 &#xff08;4&#xff09;建…

centos上面用docker 安装nacos 1.4.1

方式一&#xff1a; 下载nacos本地文件 1. 去官网下载GitHub - alibaba/nacos: an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications.2.上传到Linux服务器中cd /usr/uploadtar -zxvf nacos-serv…

二、QML工程之初始工程代码分析

接着上一讲&#xff0c;当建立完工程之后&#xff0c;IDE 会呈现如下的界面下面的代码是main.cpp&#xff0c;工程起始运行的代码段&#xff0c;具体的函数说明都在代码段里面进行了标注。#include <QGuiApplication> //主要是ui进程运行头函数&#xff0c;包含事件循环&…

【云原生】k8s图形化管理工具之rancher

内容预知 前言 1. Rancher的相关知识 1.1 Rancher的简介 1.2 Rancher与k8s的关系及区别 1.3 Rancher具有的优势 2. Rancher的安装部署 2.1 实验环境与部署图分配 2.2 具体的部署操作 &#xff08;1&#xff09;在 rancher 节点下载 rancher 镜像 &#xff08;2&#xff…

【Spring(七)】详细了解Spring的核心容器

文章目录前言容器总结前言 前面我们完成bean以及依赖注入的相关学习&#xff0c;现在我们进入到核心容器的最后一块内容了&#xff0c;也就是与容器相关操作的学习&#x1f388;&#x1f388;。 容器 前边我们获取容器是这样获取的&#x1f447;&#x1f447;。 这只是获取容器…

Vue CLI脚手架

1、Vue的开发模式 目前我们使用vue的过程都是在html文件中&#xff0c;通过template编写自己的模板、脚本逻辑、样式等。 但是随着项目越来越复杂&#xff0c;我们会采用组件化的方式来进行开发&#xff1a; 这就意味着每个组件都会有自己的模板、脚本逻辑、样式等&#xff…

凌恩生物报告升级,科研美图助力群落互作机制研究

2022年&#xff0c;在多位技术支持与生信工程师的通力合作下凌恩生物报告升级重磅升级&#xff01;扩增子分析流程项目从60到120&#xff0c;可谓是加量不加价的超值更新了&#xff01;结题报告的结果图片可直接用于文章发表&#xff0c;快一起来看看&#xff01;小小的举个例子…

pytorch【Conv2d参数介绍】

def __init__(self,in_channels: int,out_channels: int,kernel_size: _size_2_t,stride: _size_2_t 1,padding: _size_2_t 0,dilation: _size_2_t 1,groups: int 1,bias: bool True,padding_mode: str zeros # TODO: refine this type):in_channels&#xff1a;网络输入…

《Linux性能优化实战》学习笔记 Day03

04 | 零拷贝&#xff1a;如何高效地传输文件&#xff1f; 原文摘抄 文件拷贝 上下文切换的成本并不小&#xff0c;虽然一次切换仅消耗几十纳秒到几微秒&#xff0c;但高并发服务会放大这类时间的消耗。 每周期中的 4 次内存拷贝&#xff0c;其中与物理设备相关的 2 次拷贝是…

再学C语言37:函数、数组和指针

编写一个对数组进行操作的函数&#xff0c;并进行调用 一般形式举例&#xff1a; int sum(int * arr, int n); // 函数原型// 第一个参数把数组地址和类型信息传递给函数// 第二个参数把数组中元素的个数传递给函数int main(void) {...int total;total sum(array_t, 10);...…

分分钟你也能写一个自己需要的 Chrome 扩展程序

废话 对于chrome浏览器想必大家大不陌生了&#xff0c;里面的扩展程序&#xff08;本文后面称插件&#xff09;&#xff0c;多多少少也都用过吧&#xff0c;毕竟可以辅助自己的日常工作&#xff0c;娱乐等等&#xff0c;添加完之后呢&#xff0c;就会多出一些操作按钮&#xf…

年度总结-你觉得什么叫生活?

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;生活就像海洋,只有意志坚强的人,才能到达彼岸。&#x1f468;‍&#x1f4bb;上一篇文章…