AQS ReentrantLock 实现原理

参考链接

文章目录

  • 1 AQS (AbstractQuenedSynchronizer)
  • 2 Lock 接口与显式条件
  • 3 转账 Demo:解决死锁的两种方案
  • 4 ReentrantLock 非公平锁加锁流程
  • 5 ReentrantLock 和 synchronized 的异同
  • 6 ReentrantReadWriteLock

1 AQS (AbstractQuenedSynchronizer)

在这里插入图片描述

  • 基于 AQS 的同步器类,包含“获取”和“释放”操作(“获取”需要依赖状态,通常会阻塞;“释放”不会阻塞)
    • 对于 ReentrantLockSemaphore,“获取”和“释放”比较直观
    • 对于 CountDownLatch,“获取”表示“等待直到闭锁到达结束状态”,即 countDownLatch.await()
    • 对于 FutureTask,“获取”表示“等待直到任务完成”,即 futureTask.get()
boolean acquire() throws InterruptedException {while (当前状态不允许获取) {if (需要阻塞获取请求) {将当前线程加入队列并阻塞} else {return false;}}更新同步器状态如果当前线程位于队列,将线程移出队列return true;
}void release() {更新同步器状态if (当前状态允许某些被阻塞的线程获取) {解除队列中若干个线程的阻塞状态}
}
  • AQS 维护状态 volatile int state 和一个 FIFO 的线程等待队列,多线程争用资源被阻塞的时候就会进入这个队列;队列头部的线程执行完毕之后,它会调用它的后继的线程
    • ReentrantLock 将它作为锁的重入次数(因此 ReentrantLock 还要记录锁的持有者)
    • Semaphore 将它作为剩余的许可数量
    • FutureTask 用它表示任务的状态:尚未开始、正在运行、已完成、已取消
    • CountDownLatich 将它作为计数值
  • 线程通过 CAS 改变状态符 state,成功则获取锁成功,失败则进入等待队列,等待被唤醒
  • AQS 采用 自旋锁 的机制

2 Lock 接口与显式条件

public interface Lock {/*** 阻塞直到加锁成功**/void lock();/*** 解锁**/void unlock();/*** lock方法的响应中断版本:阻塞直到加锁成功或被中断**/void lockInterruptibly() throws InterruptedException;/*** 非阻塞(调用后立即返回),成功获取锁返回true,否则返回false**/boolean tryLock();/*** tryLock的延时等待版本:等待时间内可以响应中断,如果发生中断则抛出异常**/boolean tryLock(long time, TimeUnit unit) throws InterruptedException;/*** 新建一个显式条件**/Condition newCondition();
}
  • Condition 接口的关键方法:await()signal()/signalAll()
    • 类比 wait()notify()/notifyAll()
    • 调用 await()/signal() 要先加锁,否则会抛出异常(类比 wait()/notify() 要在同步代码块中调用)
    • 同样,从 await() 返回后,线程重新获得锁,但需要检查等待条件(因此总是在循环中检查条件,调用 await()
  • 显式条件和显式锁配合,synchronizedwait/notify 配合
  • 类比 wait()/notify() 的 Demo
    static void awaitSignalTest() throws InterruptedException {WaitThreadWithLock thread = new WaitThreadWithLock();thread.start();Thread.sleep(2000L);thread.setFlagTrue();  // 主线程调用setFlagTrue()}class WaitThreadWithLock extends Thread {private boolean flag = false;private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();@Overridepublic void run() {lock.lock();try {while (!flag) {try {System.out.println("sub thread call await()");condition.await();System.out.println("sub thread return from await()");} catch (InterruptedException e) {System.out.println("await interrupted");}}} finally {lock.unlock();System.out.println("sub thread terminate");}}public void setFlagTrue() {  // 由主线程调用lock.lock();try {flag = true;condition.signal();} finally {lock.unlock();System.out.println("main thread call signal()");}}
}结果:
sub thread call await()
main thread call signal()
sub thread return from await()
sub thread terminate

3 转账 Demo:解决死锁的两种方案

  • 账户类定义(不考虑余额不足的情况)
class BankAccount {private final long uid;private volatile long balance;private final Lock lock = new ReentrantLock();public BankAccount(long uid, long initBalance) {this.uid = uid;this.balance = initBalance;}public void add(long money) {accountTryLock();try {balance += money;} finally {accountUnlock();}}public void reduce(long money) {accountTryLock();try {balance -= money;} finally {accountUnlock();}}public boolean accountTryLock() {return lock.tryLock();}public void accountLock() {lock.lock();}public void accountUnlock() {lock.unlock();}public long getUid() {return uid;}public long getBalance() {return balance;}
}
  • 转账方案1,无同步措施
	/*** 无同步转账*/static void naiveTransfer(BankAccount from, BankAccount to, long money) {from.accountLock();try {to.accountLock();try {from.reduce(money);to.add(money);} finally {to.accountUnlock();}} finally {from.accountUnlock();}}
  • 转账方案2,固定加锁顺序,先获取 id 更小的账户的锁
	/*** 确定加锁顺序的转账*/static void transferWithOrder(BankAccount from, BankAccount to, long money) {// 按照uid指定加锁顺序,先获取uid更小的账户的锁BankAccount first = from.getUid() < to.getUid() ? from : to;BankAccount second = from.getUid() > to.getUid() ? from : to;first.accountLock();try {second.accountLock();try {from.reduce(money);to.add(money);} finally {second.accountUnlock();}} finally {first.accountUnlock();}}
  • 转账方案3,使用 tryLock() 转账
    • 需要注意的是,每执行一次 transferWithTryLock 不一定成功转账(因为 tryLock() 没有获取锁时,直接从方法返回)
	/*** 使用tryLock的转账*/static boolean transferWithTryLock(BankAccount from, BankAccount to, long money) {if (from.accountTryLock()) {try {if (to.accountTryLock()) {try {from.reduce(money);to.add(money);return true;} finally {to.accountUnlock();}}} finally {from.accountUnlock();}}return false;}

4 ReentrantLock 非公平锁加锁流程

  • 非公平锁是指新来的线程跟 AQS 队列头部的线程竞争锁,队列其他的线程还是正常排队
  • 公平锁严格执行 FIFO,新线程只能加入队尾

在这里插入图片描述

  1. 非公平锁尝试加锁,即执行 tryAcquire() 的流程是:检查state字段,若为0,表示锁未被占用,尝试占用锁;若不为0,检查当前锁是否被自己占用,若被自己占用,则更新 state 字段,重入次数加 1
  2. 如果以上两点都没有成功,则获取锁失败,进入等待队列
  3. 进入等待队列的线程尝试获取锁(最靠前的线程才有资格尝试),如果获取成功则成为队列新的头节点,获取失败则尝试挂起
  4. 线程入队后能够挂起的前提是,它的前驱节点的状态为 SIGNAL,状态为 SIGNAL 的节点在出队后会唤醒后面紧邻的节点

5 ReentrantLock 和 synchronized 的异同

在这里插入图片描述

  • 都提供了互斥性内存可见性
  • 优先使用 synchronized,不满足要求时考虑 ReentrantLock
  • 响应中断
    • 如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可以让它中断自己或者在别的线程中中断它
    • Lock 等待锁过程中可以用 interrupt() 来中断等待
    • Lock 接口提供的定时加锁方法 tryLock(time, timeUnit)lockInterruptibly() 具有响应中断的能力
  • 超时等待
    • 规定超时等待时间,避免线程无限期的等待获取锁
    • Lock 接口提供的定时加锁方法 tryLock(time, timeUnit)
  • 公平锁与非公平锁
    • 公平锁是指多个线程同时尝试获取同一把锁时,按照线程达到的先后顺序获取锁
    • 而非公平锁则允许线程“插队”,具体是新线程和队首的线程竞争锁
  • 自动释放
    • 内置锁以代码块为单位加锁,离开同步代码块自动释放锁
    • Lock 接口必须在 finally 块中释放锁,而不会自动释放
      private final Lock lock = new ReentrantLock();public void test() {lock.lock();try {// ...} finally {lock.unlock();}
      }
      
  • 设计思维
    • synchronized 是一种阻塞式算法,线程得不到锁的时候进入锁等待队列,等待其它线程唤醒,有上下文切换开销
    • 基于 CAS 的算法(AQS、原子变量等)是非阻塞的,如果发生更新冲突只是返回失败,不会阻塞,没有上下文切换的开销

6 ReentrantReadWriteLock

  • ReadWriteLock 接口暴露两个锁对象,读不互斥,写互斥
public interface ReadWriteLock {/*** Returns the lock used for reading.** @return the lock used for reading*/Lock readLock();/*** Returns the lock used for writing.** @return the lock used for writing*/Lock writeLock();
}
  • 用读写锁包装 Map
class ReentrantReadWriteLockMap<K, V> {private final Map<K, V> map = new HashMap<>();private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();public V put(K key, V value) {writeLock.lock();try {return map.put(key, value);} finally {writeLock.unlock();}}public V get(K key) {readLock.lock();try {return map.get(key);} finally {readLock.unlock();}}
}

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

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

相关文章

TensorFlow 2学习和工业CV领域应用 心得分享

我是一名来自苏州的机器视觉开发者&#xff0c;从事传统的机器视觉算法开发有11年了&#xff0c;从2018年开始&#xff0c;因为一些复杂微弱的瑕疵检测项目遇到的传统算法瓶颈&#xff0c;开始接触到了深度学习&#xff0c;并选择了使用TensorFlow&#xff0c;期间也是不断摸索…

历史版本_新版本爆料第弹丨英雄练习新去处,荣耀历史秀出来!

《万物有灵》新版本即将到来新版本来临之前妲己宝宝给自己定下了2个小目标&#xff01;via.小五怎么不开心目标一扩展自己小得可怜的英雄勺成为一名拥有英雄海的补位大神目标二通过自己的实力获得N1个响当当的荣耀称号很多召唤师会有疑问&#xff1a;凭妲己宝宝的实力&#xff…

循环遍历多层json_面试官:JSON.stringify() 实现深拷贝有什么问题

为什么要进行深拷贝JS中的变量在内存中存储分为值类型和引用类型&#xff1a; 值类型&#xff1a; 1、占用空间固定&#xff0c;保存在栈中&#xff1b; 2、保存与复制的是值本身&#xff1b; 3、基本类型数据是值类型&#xff08;String,Number,undefined,Boolean,Null&#x…

.NET架构小技巧(6)——什么是好的架构

首先声明&#xff0c;可能本篇文章的含金量配不上这个标题&#xff0c;因为说起架构&#xff0c;可能大家都比较关注高大上的架构&#xff0c;比如分布式的&#xff0c;高并发的&#xff0c;低耦合的&#xff0c;易扩展的等等&#xff0c;本篇可能使你失望了&#xff0c;因为这…

电子工程系庆贺电贺信_创造下一代光电子集成电路

全球互联网正以每年24%的复合速度增长&#xff0c;到2021年将达到每年3.3 zb字节。高速光通信在这个不断连接的世界中是迫切需要的&#xff0c;为了跟上这种增长&#xff0c;光模块的制造的发展是迫切需要的。复旦大学电子工程系博士研究生刘晓研究了集成构成光模块的电子电路和…

禁用笔记本键盘_如何禁用/启用笔记本内置键盘?

有些小伙伴外接了USB键盘想屏蔽掉笔记本的内置键盘&#xff0c;绞尽脑汁都没有办法禁用&#xff0c;其实方法很简单只需要一个简单的命令即可。1、右键点击左下角开始图标(WinX)&#xff0c;选择Windows Powershell(管理员)。2、在打开的窗口中&#xff0c;输入cmd。3、然后输入…

IdentityServer4系列 | 资源密码凭证模式

一、前言从上一篇关于客户端凭证模式中&#xff0c;我们通过创建一个认证授权访问服务&#xff0c;定义一个API和要访问它的客户端&#xff0c;客户端通过IdentityServer上请求访问令牌&#xff0c;并使用它来控制访问API。其中&#xff0c;我们也注意到了在4.x版本中于之前3.x…

深入探究ASP.NET Core Startup的初始化

前言Startup类相信大家都比较熟悉,在我们使用ASP.NET Core开发过程中经常用到的类&#xff0c;我们通常使用它进行IOC服务注册&#xff0c;配置中间件信息等。虽然它不是必须的&#xff0c;但是将这些操作统一在Startup中做处理&#xff0c;会在实际开发中带来许多方便。当我们…

【源码】常用的人脸识别数据库以及上篇性别识别源码

上一篇《使用ML.NET模型生成器来完成图片性别识别》发布后&#xff0c;很多朋友希望得到源码&#xff0c;这里附上地址&#xff1a;https://github.com/xin-lai/GenderRecognition常用的人脸数据库对于部分朋友说&#xff0c;找不到训练的数据&#xff0c;这里也给出部分数据&a…

程序员过关斩将--真的可以用版本号的方式来保证MQ消费消息的幂等性?

灵魂拷问MQ消息的消费为什么有时候要求幂等性&#xff1f;你们都说可以用版本号来解决幂等性消费&#xff1f;什么才是消息幂等性消费的根本性问题&#xff1f;随着系统的复杂性不断增加&#xff0c;多数系统都会引入MQ来进行解耦&#xff0c;其实从引入MQ的初衷来说&#xff0…

spring的钩子_spring提供的钩子,你知道哪些

俗话说得好“工欲善其事必先利其器”&#xff0c;现如今springboot与springcloud已成为快速构建web应用的利器。作为一个爪洼工程师&#xff0c;知道如下的spring扩展点&#xff0c;可能会让你编写出扩展性、维护性更高的代码。spring提供的钩子&#xff0c;你知道哪些bean的生…

.Net 5性能改进

起因在.Net Core跳过4.0,避免和先.Net Framework 4.0同名,版本号变为5.0,同时也不在叫.Net Core改为.Net 5(统一的叫法),先看看官方对.Net版本规划.本文主要是根据https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/ 翻译而来.不完全翻译.顺序也有所调…

开放数字世界中的复杂图数据挑战 —— 以教育与开源场景为例

摘要&#xff1a;开源开放的数字世界开始成为时代的潮流&#xff0c;云原生、数据中台、智能PRA开始成为数字世界中的新一代中流砥柱。随着第四范式的普遍流行&#xff0c;各个行业中的数字化转型都会带了海量的具有无限关联的复杂图数据。本报告将以教育与开源两个场景为例&am…

在IIS中部署SPA应用,多么痛的领悟!

目前公司的Web项目是SPA应用&#xff0c;采用前后端分离开发&#xff0c;所以有时也会倒腾Vue框架。“前后端应用最终以容器形态、在k8s中部署, 为此我搭建了基于Gitlab flow的Devops流程。在Devops实践中&#xff0c;容器部署成为良方和事实标准。但是在开发和自测阶段&#x…

mysql闪回工具下载_MySQL闪回工具之myflash 和 binlog2sql

实践利用binlog2sql查询两个binlog之间的SQL&#xff1a;必须是两个binlog日志&#xff0c;指定start-file和stop-filebinlog2sql -h127.0.0.1 -P3309 -udba -pxxxxxx -dsakila -t employee --start-filemysql-bin.000112 --stop-filemysql-bin.000113 > /tmp/db.sql利用bin…

MySQL大表优化方案

背景阿里云RDS FOR MySQL&#xff08;MySQL5.7版本&#xff09;数据库业务表每月新增数据量超过千万,随着数据量持续增加,我们业务出现大表慢查询,在业务高峰期主业务表的慢查询需要几十秒严重影响业务方案概述一、数据库设计及索引优化MySQL数据库本身高度灵活&#xff0c;造成…

使用Azure静态Web应用部署Blazor Webassembly应用

上一次演示了如何使用Azure静态web应用部署VUE前端项目&#xff08;使用Azure静态web应用全自动部署VUE站点&#xff09;。我们知道静态web应用支持VUE&#xff0c;react&#xff0c;angular等项目的部署。除了支持这些常见前端框架&#xff0c;静态web应用同样支持微软推出的最…

TIOBE 11 月榜单:Python 挤掉 Java,Java的下跌趋势确立了?

喜欢就关注我们吧&#xff01;TIOBE 公布了 2020 年 11 月的编程语言排行榜。Python 已成功跃居榜单第二名&#xff0c;本月排名率为 12.12%&#xff1b;Java 被挤到第三位&#xff0c;排名率降至 11.68%。自有 TIOBE 榜单以来&#xff0c;C 和 Java 之前一直占据着前两名的位置…

一路踩坑,被迫聊聊 C# 代码调试技巧和远程调试

一&#xff1a;背景 1. 讲故事每次项目预交付的时候&#xff0c;总会遇到各种奇葩的坑&#xff0c;我觉得有必要梳理一下以及如何快速解决的&#xff0c;让后来人避避坑&#xff0c;这篇就聊聊自己的所闻所遇&#xff1a;我去&#xff0c;本地环境代码跑的哧溜&#xff0c;上了…

mysql decimal型转化为float_5分钟搞懂MySQL数据类型之数值型DECIMAL类型

速成指南5分钟搞懂MySQL数据类型之数值型--DECIMAL类型DECIMAL类型的语法&#xff1a;DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]。其中M指定的是数字的总位数(精度&#xff0c;最大65&#xff0c;默认值10)&#xff0c;D指定的是小数点后数字的位数(最大30&#xff0c;并且不能大…