ReentrantLock相关知识

加锁流程

公平锁加锁流程:

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

加锁流程主要分三大步:

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,则首先判断一下需不需要排队hasQueuedPredecessors(),因为你获取锁的时候,有可能持有锁的线程刚好释放锁,这时你也要看一下有没有人在排队。判断是否需要排队的逻辑如下:

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 orderNode h = head;Node s;return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}

如果队首==队尾(h == t),则同步队列为空,不需要排队,直接取获取锁;如果第一个排队的人(s = h.next)是自己(s.thread == Thread.currentThread()),则也不需要排队,尝试去获取锁;中间的判断条件:(s = h.next) == null主要是考虑到并发的情况,因为你在尝试加锁的时候,有可能别的线程已经在执行入队操作(调用addWaiter方法),addWaiter方法如下:

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

入队操作构造节点Node信息是分好几步:①node.prev指向队尾②compareAndSetTail把为节点tail指向当前节点③pred.next = node;把头节点指向新加入节点。加入②执行完后,还没有执行到③,则其他线程判断(s = h.next)就有可能为空,因为此时连接还没有建立,但是此时你也是需要排队的

如果hasQueuedPredecessors()判断出不需要排队,则执行compareAndSetState(0, acquires)尝试cas改变state的值,改变成功则获取锁成功;否则执行锁重入逻辑:如果当前持有锁的线程是本线程,则state+1,state是int类型,最大值为2^31-1,如果超过此值,则抛异常,可见重入锁的最大重入次数为2^31-1。

获取锁失败就入队addWaiter

入队代码如下:

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

if条件:pred != null表示当前队列已经被初始化了,则把自己放入队尾;如果队列没有被初始化,则执行enq(node):

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

死循环表示一定要入队成功

入队完了park

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 GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}
}

如果入队成功的节点的上一个节点是头节点(p == head),这家伙还不死心,再次尝试获取锁(tryAcquire),因为入队也是需要时间的,所以如果我排队第一,有可能前面的人已经释放了锁,我就不需要park了,再次尝试获取锁。如果获取不到,则把自己park掉。park掉之前有一个方法:shouldParkAfterFailedAcquire:

/*** Checks and updates status for a node that failed to acquire.* Returns true if thread should block. This is the main signal* control in all acquire loops.  Requires that pred == node.prev.** @param pred node's predecessor holding status* @param node the node* @return {@code true} if thread should block*/
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;
}

此方法主要是改前一个节点的waitStatus变量。第一次进来int ws = pred.waitStatus;前一个节点的waitStatus=0,则执行compareAndSetWaitStatus(pred, ws, Node.SIGNAL);改成-1,表示你执行完了要唤醒我!!然后返回false;第二次循环则if (ws == Node.SIGNAL)成立,直接返回true,执行parkAndCheckInterrupt()逻辑,调用内核阻塞当前线程。

park/unpark原理

park函数作用是将当前调用线程阻塞,unpark函数则是将指定线程线程唤醒。

每个线程都会关联一个Parker对象(C代码实现),每个Parker对象都各自维护了三个角色:_counter计数器,_mutex互斥量,_cond条件变量;

park是等待一个许可,unpark 是为某线程提供一个许可。如果某线程A调用park,那么除非另外一个线程调用unpark(A) 给A一个许可,否则线程 A 将阻塞在 park 操作上。每次调用一次 park,需要有一个unpark来解锁,并且unpark可以先于park调用,但是不管unpark先调用几次,都只提供一个许可,不可叠加,只需要一次park来消费掉unpark带来的许可,再次调用会阻塞。

park的执行过程简要如下:

mutex和condition保护了一个叫_counter的信号量。当park时,这个变量被设置为0,当 unpark 时,这个变量被设置为1。(1)调用park;(2)检查_counter,当 _counter=0 时,获取mutex互斥锁,线程阻塞,当 _counter > 0 直接设为 0 并返回;(3)将线程放到_cond阻塞队列中,此时线程处于阻塞状态;(4)虽然此时_counter已经等于0了,但还要再设置一遍,让其等于0

unpark简要流程:

(1)unpark直接设置_counter为1,再 unlock mutex 返回。(2)如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程;(3)Thread-0恢复运行;(4)设置_counter等于0

条件唤醒condition

调用reentrantLock.newCondition();会返回一个Condition对象,ReentrantLock维护了若干个条件队列,可以实现条件唤醒。

和synchronized的区别

1、synchronized可以修饰方法、代码块,而ReentrantLock只能修饰代码块;

2、synchronized自动加锁和释放锁,ReentrantLock需要手动加锁和释放锁;

3、synchronized是非公平锁,ReentrantLock默认是非公平锁,可以配置为公平锁;

4、synchronized不能响应中断,ReentrantLock可以;

5、synchronized搭配wait(),notify(),notifyAll()一起使用,锁池是先进后出的栈结构;ReentrantLock维护了一个等待队列和多个条件队列,

6、ReentrantLock支持条件唤醒,synchronized不支持

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

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

相关文章

C++nullptr

其实理解nullptr很简单&#xff0c;它其实就是将一个指针置为空 int* arrnullptr; 但是为什么C语言明明有NULL可以将指针置为空&#xff0c;C还要引入nullptr呢 其实简单理解C语言的NULL它其实是一个宏 #ifndef NULL #ifdef __cplusplus #define NULL 0 #else #define NUL…

【Agent】Cognitive Architectures for Language Agents

arxiv: https://arxiv.org/abs/2309.02427 背景 现有的Agent框架&#xff0c;大部分是基于强化学习提出的框架。本文结合生产系统和认知科学&#xff0c;提出了一个结构化和模块化的Agent架构。 2、记忆 记忆可分为两类&#xff1a; 工作记忆&#xff08;短期记忆&#xf…

多线程股吧(东方财富)用户信息爬取

多线程东方财富&#xff08;股吧&#xff09;用户信息爬取 在上一篇博客股吧信息爬取的基础上加入了多线程&#xff0c;使得速度提升了十几倍&#xff0c;爬取内容如下&#xff1a; 最终爬取结果如下&#xff1a; 完整代码如下&#xff08;准备好环境&#xff0c;比如pytho…

近年来自动驾驶行业就业与企业需求情况

自动驾驶行业在近年来持续发展&#xff0c;就业情况和企业需求呈现出多样化和复杂化的趋势。 以下是基于我搜索到的资料对自动驾驶行业最新就业情况和企业需求的详细分析&#xff1a; 自动驾驶行业对高端技术人才的需求非常旺盛&#xff0c;尤其是架构工程师、算法工程师等岗…

Qt(10.8)

作业&#xff1a;完善登录界面 源文件 #include "widget.h" #include "ui_widget.h" #include<QDebug> #include<QLabel> #include<QMessageBox> Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setu…

【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则

文章目录 C 继承详解&#xff1a;初阶理解与实战应用前言第一章&#xff1a;继承的基本概念与定义1.1 继承的概念1.2 继承的定义 第二章&#xff1a;继承中的访问权限2.1 基类成员在派生类中的访问权限2.2 基类与派生类对象的赋值转换2.2.1 派生类对象赋值给基类对象2.2.2 基类…

leetcode68:文本左右对齐

给定一个单词数组 words 和一个长度 maxWidth &#xff0c;重新排版单词&#xff0c;使其成为每行恰好有 maxWidth 个字符&#xff0c;且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词&#xff1b;也就是说&#xff0c;尽可能多地往每行中放置单词。必要时可…

Ubuntu 22.04 安装 KVM

首先检查是否支持 CPU 虚拟化&#xff0c;现在的 CPU 都应该支持&#xff0c;运行下面的命令&#xff0c;大于0 就是支持。 egrep -c (vmx|svm) /proc/cpuinfo安装 Libvirt apt install -y qemu-kvm virt-manager libvirt-daemon-system virtinst libvirt-clients bridge-uti…

DAMA数据管理知识体系(第11章 数据仓库和商务智能)

课本内容 11.1 引言 概要 数据仓库被公认为企业数据管理的核心语境关系图 图11-1 语境关系图&#xff1a;数据仓库和商务智能业务驱动因素 运营支持职能合规需求商务智能活动目标和原则 目标 一个组织建设数据仓库的目标通常有&#xff1a; 1&#xff09;支持商务智能活动。 2&…

易图讯军用VR三维电子沙盘系统

深圳易图讯军用VR三维电子沙盘系统是一种集成了虚拟现实&#xff08;VR&#xff09;技术、三维建模技术、大数据分析、实时动态更新以及高度安全可靠的综合性军事指挥平台。该系统通过高精度三维模型真实再现战场环境&#xff0c;为指挥员提供沉浸式体验和交互操作的可能性&…

数据结构与算法——Java实现 31.阻塞队列

—— 24.10.8 一、问题提出 目前队列存在的问题 1.很多场景要求分离生产者、消费者两个角色、它们需要由不同的线程来担当&#xff0c;而之前的实现根本没有考虑线程安全问题 2.poll方法&#xff0c;队列为空&#xff0c;那么在之前的实现里会返回null&#xff0c;如果就是硬…

构建MySQL健康检查Web应用

构建MySQL健康检查Web应用 在这里将探讨如何将MySQL健康检查功能转换为一个功能完整的Web应用。这个应用允许用户通过简单的Web界面执行MySQL健康检查&#xff0c;并查看详细的结果。我们将逐步介绍代码实现、改进过程以及如何设置和运行这个应用。 1. MySQL健康检查类 首先…

codetop标签双指针题目大全解析(二),双指针刷穿地心!!!!!

复习比学习更重要&#xff0c;如果忘了就跟没学是一样的 1.和为k的子数组2.统计[优美子数组]3.区间列表的交集4.将x减到0的最小操作5.替换子串得到平衡字符串6.划分字母区间7.分隔链表8.通过删除字母匹配到字典里最长单词9.寻找目标值-二维数组10.至多包含两个不同字符的最长子…

麒麟系统串口配置篇

麒麟系统串口配置篇 1.配置串口驱动&#xff08;编译/动态加载串口&#xff09; 解压文件夹,然后在解压后的文件夹所在目录&#xff0c;右键选择打开终端&#xff0c;依次执行以下命令&#xff1a; 以麒麟系统下的CH341串口驱动为例&#xff0c;解压CH341SER_LINUX.zip sudo…

2024_10_8 系统进展

改进位置 发现是label_api里藏了我需要改进的东西 settings.py 数据库 我这边电脑上使用的是windows 192 vue.config.js 陈家强是这样设置的 module.exports {publicPath: process.env.NODE_ENV production? /: /,assetsDir: static,// css: {// extract: false// },…

【C++ 11】for 基于范围的循环

文章目录 【 1. 基本用法 】【 2. for 新格式的应用 】2.1 for 遍历字符串2.2 for 遍历列表2.3 for 遍历的同时修改元素 问题背景 C 11标准之前&#xff08;C 98/03 标准&#xff09;&#xff0c;如果要用 for 循环语句遍历一个数组或者容器&#xff0c;只能套用如下结构&#…

“我养你啊“英语怎么说?别说成I raise you!成人学英语到蓝天广场附近

“我养你啊”这句经典台词出自周星驰自导自演的电影《喜剧之王》。在这部电影中&#xff0c;周星驰饰演的尹天仇对张柏芝饰演的柳飘飘说出了这句深情而动人的台词。这句台词出现在柳飘飘即将离去之时&#xff0c;尹天仇鼓起勇气&#xff0c;用它作为对柳飘飘个人困境的承诺&…

VIP与MPIO,备胎管理谁更强

我爱上班&#xff0c;风雨无阻 大家每天去上班&#xff0c;不可能只有一条路线 可以地铁、也可以开车或公交 万一地铁停运或车子限行&#xff0c;至少还有其他线路选择 企业级存储也是如此 关键业务的存储访问一般有多条路径 网络或单个存储设备故障后 访问路径会自动切换…

集合框架05:List接口使用、List实现类、ArrayList使用

视频链接&#xff1a;13.11 ArrayList使用_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1zD4y1Q7Fw?p11&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.List接口使用代码举例 package com.yundait.Demo01;import java.util.ArrayList; import java.util.List;pu…

轻松掌握IP代理服务器设置方法,网络冲浪更自如

在数字化时代&#xff0c;互联网就像是一片浩瀚的海洋&#xff0c;而IP代理服务器就如同我们在这片海洋中航行的指南针。通过使用代理IP&#xff0c;我们可以更方便地访问全球网络资源&#xff0c;提升网络安全性。本文将为您详细介绍IP代理服务器的设置方法&#xff0c;让您在…