常见的锁策略,synchronized优化过程和cas过程

1. 常见的锁策略

所谓"策略",也可以理解为做法."锁策略"就是用来描述一把锁面对加锁/解锁时的做法.

1.1 乐观锁 vs 悲观锁

要区分一把锁是乐观锁还是悲观锁,就要预测当前这把锁冲突的概率高不高.

如果冲突概率高,后续要做的工作往往会更多,加锁的开销就大,这就是悲观锁.

如果冲突概率不高,后续要做的工作往往就会少,加锁的开销就少,这就是乐观锁.

那么java中的synchronized是属于哪种锁呢? 答:既是乐观锁,又是悲观锁.

这是因为synchronized支持自适应,能够自己统计出当前锁冲突的次数,进行判定当前锁冲突概率高不高.如果冲突概率高,就按照悲观锁的方式执行;如果冲突概率低,就按照乐观锁的方式执行.

1.2 重量级锁 vs 轻量级锁

区分一把锁是重量级锁还是轻量级锁看的是:对这把锁加锁过程中做的事情多不多.

如果做的事情多,就是重量级锁;如果做的事情少,就是轻量级锁.

一般来说,悲观锁往往是重量级锁;乐观锁往往是轻量级锁.也许以后我们再使用时会混着用这两对概念,但是我们要理解这两对概念的出发点时不同的.

1.3 自旋锁 vs 挂起等待锁

自旋锁,是轻量级锁的一种典型的实现方式.

内部的过程,我们这里用伪代码演示一下:

void lock() {while(true) {if(锁被占用) {continue;}获取到锁break;}
}

这个过程中,cpu在空转忙等.消耗了更多的cpu资源.但是,一旦锁被释放,就可以第一时间拿到锁,拿到锁的速度更快.

挂起等待锁,是重量级锁一种典型的实现方式.

所谓"挂起等待',就是在发生锁冲突的时候,进入阻塞等待状态.直到这把锁被释放,系统才能唤醒这个线程去获取这把锁.在这个过程中,由于这个线程说明时候被唤醒是不确定的,因此会花费更长的时间去获取到这把锁,但是cpu不会进行空转,节省了cpu资源.

synchronized的轻量级锁部分,通过自旋锁实现;重量级锁部分,通过挂起等待锁实现.

1.4 可重入锁 vs 不可重入锁

所谓"可重入"和"不可重入"就是同一个线程对这把锁加锁两次,看会不会发生死锁.

不会死锁的就是"可重入锁",会死锁的就是"不可重入锁".

synchronized就是一种可重入锁.这一点我们在之前的博客中已经介绍过了,这里就不过多介绍.

1.5 公平锁 vs 非公平锁

对于公平锁,讲究的是"先来后到",哪个线程等待这把锁的时间最长,哪个线程先获取到这把锁.

对于非公平锁,讲究的是"各凭本事",当这把锁被释放后,哪个线程竞争力强,就哪个线程先获取到这把锁.

synchronized是非公平锁,当这把锁被释放时,多个线程获取到这把锁的概率是被均分的.如果要实现公平锁,需要设置一个线程队列,这样才能排出个"先来后到".

1.6 互斥锁 vs 读写锁

synchronized就是普通的互斥锁.操作:加锁/解锁.

读写锁时一种更特殊的锁.操作:加读锁/加写锁/解锁.

java中的读写锁主要可以由三句话概括:

1. 读锁和读锁之间不互斥

2. 读锁和写锁之间互斥

3. 写锁和写锁之间互斥

主要突出的是,读操作可以同时进行,共享数据.有利于降低锁冲突率,提高并发能力.因为,在日常某些场景中,写操作进行得少,而读操作会进行许多次,这是使用读写锁的优势便能够体现出来.

2. 锁优化 

我们刚才提到了synchronized既是悲观锁,也是乐观锁;既是轻量级锁,也是重量级锁;是可重入锁;是非公平锁;是互斥锁.我们这里来了解synchronized的优化过程.

2.1 锁升级

对于synchronized,会自适应地进行"锁升级",锁升级的过程如下:

未加锁状态->偏向锁->轻量级锁->重量级锁

我们来模拟一下这个过程:

原来线程是未加锁状态,执行synchronized语句升级为偏向锁;如果,遭遇了锁冲突,从偏向锁升级为轻量级锁;如果锁冲突进一步加剧,从轻量级锁升级为重量级锁.

这里我们要对偏向锁这个概念,进行一定的介绍:

这里的偏向锁,就是:加锁了吗?答:如加.实际上就是对这把锁进行一个标记,如果在运行过程中,没有别的线程来获取这把锁,就不对这个线程加锁了;如果有别的线程来获取这把锁,再真正对这个线程加锁(轻量级锁).

锁升级,避免了过度消耗系统的资源.

2.2 锁消除

这是一种编译器优化策略.当我们在代码中使用了synchronized,JVM就会进行判断,这个地方到底需不需要加锁.如果这里不需要加锁,编译器就会自动地把这里的加锁操作给优化掉.

这里最典型的情况就是,在只有一个线程的代码中使用synchronized,这里的加锁操作就会被优化掉.

但是,编译器优化前后要保证代码的逻辑是等价的,因此这里的优化起到的作用是十分有限的.

2.3 锁粗化

锁的粒度:加锁的范围内包含的代码多少.

包含的代码多,锁的粒度就粗;包含的代码少,锁的粒度就细.

在有些逻辑中,需要频繁的加锁和解锁,编译器就会把多个细粒度的锁合并成一把粗粒度的锁,这样在保证等价逻辑的前提下,减少了系统资源的消耗.

3. CAS

CAS的全称是compare and swap,意为比较和交换.这是cpu的一条指令,体现了原子性.

我们可以把CAS的过程想象成一个方法,可以结合下面的伪代码分析:

boolean cas(address,reg1,reg2) {if(*address == reg1) {把address内存地址的值和reg2寄存器的值进行交换.return true;}return false;
}

address表示获取内存地址,reg指的是cpu的寄存器.这里虽然说的是交换,实际上更多的是用来赋值,而且更关心内存中交换后的值,而不是寄存器里交换后的值.

操作系统内核可以通过cpu的这条指令提供CAS的API;JVM又对系统的CAS API进一步封装,在java代码中也就可以使用CAS操作了.典型的就是"原子类".

原子类的使用

在java.util.concurrent.atomic这个包下,就封装好了许多原子类.

如下图:

比如我们之前提过的案例:使用两个线程交替对同一个变量进行自增.我们分析过,如果不加锁去实现这个代码,会引发线程安全问题.现在我们使用原子类来操作一下:

public static void main(String[] args) throws InterruptedException {AtomicInteger count = new AtomicInteger(0);Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

效果如下:

这一套操作被称为"无锁编程",虽然在这种情况下运行效率高了,但是在某些场景下,CAS并不适用.

ABA问题

当前有两个线程并发执行修改变量num的操作,num的初始值为A.

线程t1想要把num的值修改成Z,t1就先读取到num的值存储到oldNum中,然后使用CAS判断,如果当前num的值为A就把他修改成Z.

但是在t1读取到num的值到判断是否要修改的过程中.线程t2进行了修改操作,它先把A改成了B,再把B改成了A.这样看上去好像就没有修改.但是 没有修改 != 修改前后一样.

这样等到线程t1进行判断时,发现num的值仍然是A,会进行修改.但是这与t1操作原先的期望(num的值一直为A就修改为Z)不同,就出现了bug.这就是CAS容易引起bug的"ABA问题".

时间线:

t1获取到num的值A->t2修改num的值为B->t2修改num的值为A->t1判断num的值为A,修改为Z

解决方法:

引入一个只能增加的版本号变量,我们对其中的值每进行一次修改,就让版本号加一.在判断时不仅仅判断值是否相等,同时要判断版本号是否一致.我们引入版本号再来分析一次上面的过程.

假设初始的版本号为1,t1获取到num的值为A,版本号为1;t2修改num的值为B,版本号变为2;t2修改num的值为A,版本号变为3;t1进行判断,值为A,匹配成功,版本号为3 != 1,匹配失败,不进行修改.

 

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

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

相关文章

使用阿里云试用Elasticsearch学习:4. 聚合——2

近似聚合 如果所有的数据都在一台机器上&#xff0c;那么生活会容易许多。 CS201 课上教的经典算法就足够应付这些问题。如果所有的数据都在一台机器上&#xff0c;那么也就不需要像 Elasticsearch 这样的分布式软件了。不过一旦我们开始分布式存储数据&#xff0c;就需要小心…

Docker 安装 RocketMQ

目录 一、新建两个配置文件 1.1 创建docker-compose.yml文件 1.2 .新建broker.conf文件 二、运行 三、可视化界面 一、新建两个配置文件 1.1 创建docker-compose.yml文件 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports…

用html写一个雨的特效

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>雨特效</title><link rel"stylesheet" href"./style.css"> </head> <body> <div id"wrap-textu…

43岁百亿千金夫妇国外旅游高调放闪,羡煞好友马国明

萧正楠早前忙着拍摄TVB新剧《奔跑吧&#xff01;勇敢的女人们》&#xff0c;剧集煞科后他即和老婆黄翠如一起到意大利旅行。两公婆分别在IG大晒旅行靓相&#xff0c;甜蜜满泻。 这次萧正楠的旅游照都是由翠如亲自操刀拍摄&#xff0c;相中所见&#xff0c;萧正楠在广场大摆Chok…

竞赛 协同过滤电影推荐系统

文章目录 1 简介1 设计概要2 课题背景和目的3 协同过滤算法原理3.1 基于用户的协同过滤推荐算法实现原理3.1.1 步骤13.1.2 步骤23.1.3 步骤33.1.4 步骤4 4 系统实现4.1 开发环境4.2 系统功能描述4.3 系统数据流程4.3.1 用户端数据流程4.3.2 管理员端数据流程 4.4 系统功能设计 …

AI人工智能讲师大模型培训讲师叶梓 大语言模型(LLM)在科学文献摘要领域的应用

大语言模型&#xff08;LLM&#xff09;在科学文献摘要领域的应用是一个前沿且迅速发展的技术趋势。通过结合GitHub上yobibyte的Compressor项目&#xff0c;我们可以深入探讨这一技术方案的潜力和实现方式。 技术背景 随着科学研究的快速发展&#xff0c;每天都有大量的科学文…

【软考中级】软件设计师考点分布

文章目录 软考官网资格设置软考报考流程 【软件设计师】考点分布选择题考点分布案例题考点分布 软考官网 中国计算机技术职业资格网&#xff1a;https://www.ruankao.org.cn/ 官网报名平台&#xff1a;https://bm.ruankao.org.cn/sign/welcome 资格设置 计算机软件计算机网…

QT常用控件

常用控件 控件概述QWidget 核⼼属性核⼼属性概览enabledgeometrywindowTitlewindowIconwindowOpacitycursorfonttoolTipfocusPolicystyleSheet 按钮类控件Push ButtonRadio ButtionCheck Box 显⽰类控件LabelLCD NumberProgressBarCalendar Widget 输⼊类控件Line EditText Edi…

MemberPress配置和使用会员登录页面

目录 隐藏 创建会员登录页面 编辑登录页面 设计您的登录页面 链接到您的登录页面 创建会员登录页面 要创建MemberPress会员登录页面&#xff0c;您需要做的就是导航到 MemberPress > 设置 > 页面选项卡&#xff0c;然后在页面顶部附近的“MemberPress 登录页面”…

计算机网络——ARP协议

前言 本博客是博主用于复习计算机网络的博客&#xff0c;如果疏忽出现错误&#xff0c;还望各位指正。 这篇博客是在B站掌芝士zzs这个UP主的视频的总结&#xff0c;讲的非常好。 可以先去看一篇视频&#xff0c;再来参考这篇笔记&#xff08;或者说直接偷走&#xff09;。 …

微服务边车模式深度解析:赋能云原生应用的终极指南(自己搞一个简单SideCar?)

什么是SideCar? Sidecar模式定义&#xff1a; Sidecar 模式是一种常用于微服务架构中的设计模式&#xff0c;该模式允许将应用程序的核心功能与辅助功能&#xff08;如日志记录、监控、配置管理、网络通信等&#xff09;分离开来。在这种设计模式中&#xff0c;每个微服务主容…

快团团团长如何设置起购金额?新手教程免费学!

一、功能说明 部分团长有最低起送价的需求&#xff0c;这时可以通过设置起购金额来规定团员的最低起购价。 二、具体操作步骤 &#xff08;一&#xff09;如何设置起购金额&#xff1f; 在团购设置中&#xff0c;点击“更多团购设置”&#xff0c;在“其他设置”中找到“起购…

vue3使用jsQR解析二维码

1.了解jsQR jsQR是一个纯javascript脚本实现的二维码识别库&#xff0c;不仅可以在浏览器端使用&#xff0c;而且支持后端node.js环境。jsQR使用较为简单&#xff0c;有着不错的识别率。 2.效果图 3.二维码 4.下载jsqr包 npm i -d jsqr5.代码 <script setup> import …

每日一题(leetcode209):长度最小的子数组--滑动窗口

由于是区间问题&#xff0c;考虑到使用滑动窗口。 下面是我刚开始写的代码&#xff0c;虽然能通过&#xff0c;但是思维紊乱&#xff0c;循环条件应该去盯着满足情况下的条件&#xff0c;然后每次都去比较最小值&#xff0c;这样比较简洁。 class Solution { public:int minS…

卷积神经网络结构组成与解释

卷积神经网络结构组成与解释 卷积神经网络是以卷积层为主的深度网路结构&#xff0c;网络结构包括有卷积层、激活层、BN层、池化层、FC层、损失层等。卷积操作是对图像和滤波矩阵做内积&#xff08;元素相乘再求和&#xff09;的操作。 1. 卷积层 常见的卷积操作如下&#x…

比特币减半后 牛市爆发

作者&#xff1a;Arthur Hayes of Co-Founder of 100x 编译&#xff1a;Qin jin of ccvalue (以下内容仅代表作者个人观点&#xff0c;不应作为投资决策依据&#xff0c;也不应被视为参与投资交易的建议或意见&#xff09;。 Ping PingPing&#xff0c;我的手机发出的声音&…

Servlet(一)

文章目录 1.Servlet整体框架2.Servlet快速入门1.创建项目配置基本环境2.添加jar包1.在WEB-INF下创建目录lib&#xff0c;添加文件2.添加到项目中3.配置代码提示 3.src下创建文件4.实现Servlet接口5.在web.xml配置HelloServlet6.通过浏览器访问HelloServlet 3.浏览器访问Servlet…

系统架构最佳实践 -- 供应链系统架构

供应链系统是现代企业管理中不可或缺的一部分&#xff0c;它涉及到从原材料采购到产品销售的整个生产流程。一个高效的供应链系统可以帮助企业实现成本控制、库存优化和客户满意度提升等目标。在本文中&#xff0c;我们将讨论供应链系统的设计与实践。 一、供应链系统设计 业务…

前端开发攻略---Vue通过自定义指令实现元素平滑上升的动画效果(可以自定义动画时间、动画效果、动画速度等等)。

1、演示 2、介绍 这个指令不是原生自带的&#xff0c;需要手动去书写&#xff0c;但是这辈子只需要编写这一次就好了&#xff0c;后边可以反复利用。 3、关键API IntersectionObserver IntersectionObserver 是一个用于监测元素是否进入或离开视口&#xff08;viewport&#x…

系统架构最佳实践 -- 卡券系统架构设计

目录 1.卡券类型的设计&#xff1a; 2.用户领取和使用流程&#xff1a; 3.卡券发放与管理&#xff1a; 4.卡券核销与统计&#xff1a; 5.安全性和防欺诈&#xff1a; 随着电子商务的迅速发展&#xff0c;卡券系统成为了各种商家吸引用户和促销的重要手段。在设计和实现卡券…