【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)

目录

1、锁的策略

1.1、乐观锁和悲观锁 

1.2、轻量级锁和重量级锁

1.3、自旋锁和挂起等待锁

1.4、普通互斥锁和读写锁

1.5、公平锁和非公平锁

1.6、可重入锁和不可重入锁 

2、synchronized 内部的升级与优化过程

2.1、锁的升级/膨胀

2.1.1、偏向锁阶段

2.1.2、轻量级锁阶段

2.1.3、重量级锁阶段

2.2、锁消除

2.3、锁粗化

3、CAS(Compare and swap)

3.1、CAS 的应用

3.1.1、实现 Atomic 原子类

3.1.2、实现自旋锁

3.1.3、CAS 的 ABA 问题



1、锁的策略

加锁过程中,处理冲突的过程中,涉及到的一些不同的处理方式,就叫锁的策略。

1.1、乐观锁和悲观锁 

乐观锁
在加锁之前,预估当前出现锁冲突的概率不大,因此在进行加锁的时候就不会做太多的工作。

由于加锁过程中做的事情比较少,加锁的速度可能就更快,但是更容易引入一些其他的问题(消耗更多cpu资源)。


悲观锁
在加锁之前,预估当前出现锁冲突的概率比较大,因此在进行加锁的时候就会做更多的工作。

由于加锁过程中做的事情更多,加速的速度可能更慢,但是整个过程中不容易出现其他问题

1.2、轻量级锁和重量级锁

轻量级锁
加锁的开销小,加锁的速度更快, 轻量级锁,一般就是乐观锁。


重量级锁
加锁的开销更大,加锁的速度更慢,重量级锁,一般就是悲观锁。

需要注意的是: 

悲观乐观,是在加锁之前,对未发生的事情进行的预估。
轻量重量,是在加锁之后,对结果的评价。
但是整体来说,这两种角度,描述的都是同一个事情。 

1.3、自旋锁和挂起等待锁

自旋锁
就是轻量级锁的一种典型实现,同时也是一种乐观锁。
进行加锁的时候,会搭配一个while循环,如果加锁成功,自然循环结束。
如果加锁不成功,不是阻塞放弃cpu,而是进行下一个循环,再次尝试获取到锁。
上述操作的反复快速执行的过程,就称为“自旋”。一旦其他线程释放了锁,就能第一时间拿到锁。


挂起等待锁
就是重量级锁的一种典型实现,同时也是一种悲观锁(适用于锁冲突激烈的情况)。

1.4、普通互斥锁和读写锁

普通互斥锁
类似于 synchronized 操作涉及的 加锁 和 解锁


读写锁
把加锁分为两种情况: 1)加读锁 ReadLock 2)加写锁 WriteLock
结论:读锁与读锁不会出现锁冲突(不会阻塞),写锁与写锁、读锁与写锁都会出现锁冲突(会阻塞)
这里的差异理解起来也很简单,因为两个线程进行读操作,本身就是线程安全的,所以不需要进行互斥(而且读操作在实际开发中有非常频繁,所以能够大大提升了性能), synchronized 不是读写锁,对于读写锁 JVM 同样封装了 api 给程序员使用。

1.5、公平锁和非公平锁

公平锁
先来后到叫公平,实现公平锁需要引入额外的数据结构(队列,记录先后顺序)。


非公平锁
Java中默认的锁就是非公平锁,正是因为非公平锁导致了“线程饿死”

所谓“线程饿死”,可以理解为:

        当线程A获取锁时,发现此时不能完成实质性的逻辑(取钱时发现atm没钱了),此时只能释放锁并退出,重新与其他线程竞争该锁(刚刚释放锁的线程也会参与到锁竞争中)。有一个极端情况,就是每次释放锁之后又是线程A获取到了锁,并且此时依然无法完成实质性的逻辑,又只能释放锁。就出现【线程A忙了半天不干实事,其他线程都只能干等了】的情况,这就称之为“线程饿死”。
        而这种极端情况出现的概率还挺高的,因为线程A获取锁后处在RUNNABLE状态,而其他线程处在BLOCKED状态,处于BLOCKED状态的线程需要系统唤醒之后才能参与锁竞争,而线程A不需要,因此线程A再次获取到锁的概率比其他的线程高。

1.6、可重入锁和不可重入锁 

针对一个线程一把锁,连续加锁两次不会产生死锁,就是可重入锁,反之就是不可重入锁。系统自带的锁是不可重入锁,Java 的 synchronized 是可重入锁。

2、synchronized 内部的升级与优化过程

Java 中的 synchronized 具有自适应能力。内部会自动评估当前锁冲突的剧烈程。

如果当前锁冲突的剧烈程度不大,就处在 乐观锁/轻量级锁/自旋锁。

如果当前锁冲突的剧烈程度很大,就处在 悲观锁/重量级锁/挂起等待锁。

2.1、锁的升级/膨胀

2.1.1、偏向锁阶段

类似于“懒汉模式”,能不加锁就不加锁,能晚加锁就晚加锁。在遇到竞争的情况下,偏向锁没有提高效率;但是如果在没有竞争的情况下, 偏向锁就大幅度的提高了效率。

.

【只是做了一个标记,没有真加锁(也就不存在互斥),一旦有其他线程想要来竞争锁,就在另一个线程之前先把锁获取到,进而从偏向锁升级到轻量级锁(存在互斥)。】

2.1.2、轻量级锁阶段

通过自旋锁的方式来实现优势:其他线程释放锁后能够第一时间拿到锁;劣势:消耗cpu;

.

此外 synchronized 内部也会统计当前这个锁对象上,有多少个线程产于竞争(都是自旋式竞争),如果发现参与竞争的线程比较多,就会进一步升级到重量级锁。

2.1.3、重量级锁阶段

此时拿不到锁的线程就不会继续自旋,而是进入“阻塞等待”,让出cpu资源。锁被释放后回归随机唤醒机制。

2.2、锁消除

简单来说就是自动干掉不必要的锁。

2.3、锁粗化

简单来说就是把多个细粒度的锁合并成一个粗粒度的锁,减少锁竞争的开销。

以上说的所有机制,都是在内部自动执行的,不需要程序员在编写代码的时候真正的手动执行。

3、CAS(Compare and swap)

CAS(即比较并交换)本身是特殊的单个 cpu 指令,一个 CAS 涉及以下操作:

假设内存中的原数据V,旧的预期值A,需要修改的新值B

1、比较A与V是否相等(比较)

2、如果比较相等,将B写入V(交换)

3、返回操作是否成功

由于 CAS是单个 cpu 指令,使用 CAS 可以“原子”的完成很多复杂操作,不涉及到加锁,也不会阻塞,合理的使用也可以保证线程安全,使用 CAS 的编程方式称为“无锁化编程”,是多线程编程中的特殊技巧。

操作系统对 CAS 指令进行了封装成了 api ,JVM 有对操作系统提供的 api 又封装一层。

3.1、CAS 的应用

3.1.1、实现 Atomic 原子类

Java 标准库中的 Atomic 原子类就是基于 CAS 来实现的。

伪代码描述 CAS 以及 模拟 AtomicInteger 自增过程

3.1.2、实现自旋锁

伪代码描述自旋锁的执行过程

3.1.3、CAS 的 ABA 问题

CAS 使用的时候,关键要点是判定当前内存的值和寄存器中的值是否一样,判定是否一样的操作本质上是在判定这个代码执行的过程中是否有其他线程穿插进来了。

但是这样的判定可能存在这样的可能,本来数值是0,执行 CAS 之前,另一个线程把值从 0 → 100 ,又从 100 → 0 ,虽然改了,但是又改回去了,这种情况就称为 ABA 问题。CAS 的 ABA 问题一般情况下不会有问题,不会产生啥 bug ,但是就怕一些极端情况。

解决方案:
1、约定数据变化是单向的(只能添加或者只能减少),不能是双向的。
2、对于本身就必须双向变化的数据,可以引入一个版本号,版本号是单向增加的。

【博主推荐】 

【Java多线程】对线程池的理解并模拟实现线程池-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/136160003?spm=1001.2014.3001.5501【Java多线程】分析线程加锁导致的死锁问题以及解决方案-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/136150237?spm=1001.2014.3001.5501【Java多线程】线程安全问题与解决方案-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/zzzzzhxxx/article/details/136133785?spm=1001.2014.3001.5501

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

为什么会造成服务器丢包?

随着云服务器市场的发展和网络安全问题,服务器丢包问题成为了一个普遍存在的现象。服务器丢包是指在网络传输过程中,数据包由于各种原因未能到达目标服务器,导致数据传输中断或延迟。那么,为什么会造成服务器丢包呢?下…

naive-ui-admin 表格去掉工具栏toolbar

使用naive-ui-admin的时候,有时候不需要显示工具栏,工具栏太占地方了。 1.在src/components/Table/src/props.ts 里面添加属性 showToolbar 默认显示,在不需要的地方传false。也可以默认不显示 ,这个根据需求来。 2.在src/compo…

在SAP HANA中使用OData(二)

通常有两种方式通过OData来暴露SAP HANA中的数据库对象,一是直接使用Database Object,比如前一篇和本篇文章介绍的例子,这种方式针对于数据已经存在于SAP HANA中,在Repository中没有对应的设计时对象(Design-time Object)&#xf…

基于 LVGL 使用 SquareLine Studio 快速设计 UI 界面

目录 简介注册与软件获取工程配置设计 UI导出源码板级验证更多内容 简介 SquareLine Studio 是一款专业的 UI 设计软件,它与 LVGL(Light and Versatile Graphics Library,轻量级通用图形库)紧密集成。LVGL 是一个轻量化的、开源的…

Redis String 类型底层揭秘

目录 前言 String 类型低层数据结构 节省内存的数据结构 前言 Redis 的 string 是个 “万金油” ,这么评价它不为过. 它可以保存Long 类型整数,字符串, 甚至二进制也可以保存。对于key,value 这样的单值,查询以及插…

[足式机器人]Part2 Dr. CAN学习笔记-Ch00-2 - 数学知识基础

本文仅供学习使用 本文参考: B站:DR_CAN 《控制之美(卷1)》 王天威 《控制之美(卷2)》 王天威 Dr. CAN学习笔记-Ch00 - 数学知识基础 Part2 4. Ch0-4 线性时不变系统中的冲激响应与卷积4.1 LIT System:Linear Time Invariant4.2 卷积 Convolution4.3 单位冲激 Unit Impulse—…

Linux安装Nginx详细步骤

1、创建两台虚拟机,分别为主机和从机,区别两台虚拟机的IP地址 2、将Nginx素材内容上传到/usr/local目录(pcre,zlib,openssl,nginx) 附件 3、安装pcre库   3.1 cd到/usr/local目录 3.2 tar -zxvf pcre-8.36.tar.gz 解压 3.3 cd…

matlab 方向向量约束的PCA快速粗配准

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的GPT爬虫。 一、算法原理 该方法由本人原创,目前尚未见有相关论文用到。具体原理看代码即可。 二、代码实现 clc;clear; %% ------…

在Arcgis中删除过滤Openstreetmap道路属性表中指定highway类型道路

一、导出道路类型并分析 1. 导出道路类型 选中highway属性列,选择汇总→确定 2. 分析 用Excel打开输出表,包含的道路类型如下 0.空值’’ 车辆可行驶道路(和bfmap的并集) 空值(无定义道路) 二、…

数据分析(二):学生成绩预测分析报告

目录 摘要 一、引言 二、 数据源介绍 三、 数据清洗和预处理 3.1 缺失值处理 3.2 异常值处理 3.3 数据编码 四、 探索性数据分析 4.1 可视化相关统计量 4.2 目标数据的分布情况 4.3 Pearson 相关性分析 五、 特征工程 5.1 特征构造 5.1.1 总饮酒量 5.1.2 整体关…

【前端知识点】

虚拟 dom: 虚拟 dom 就是 vue 通过 js 对象渲染虚拟 dom 的,虚拟 dom 的 js 对象包含节点的类型、属性、子节点等信息,这些虚拟 dom 节点会构成一棵树形结构,用来表示整个页面的结构。 当 vue 组件更新时,会通过 diff…

牛客前端八股文(每日更新)

1.说说HTML语义化? 得分点:语义化标签、利于页面内容结构化、利于无CSS页面可读、利于SEO、利于代码可读 1,标签语义化是指在开发时尽可能使用有语义的标签,比如header,footer,h,p&#xff0c…

Linux学习之system V

目录 一,system V共享内存 快速认识接口 shmget(shared memory get) shmat(shared memory attach) shmdt(shared memory delete) shmctl (shared memory control) 编写代码 综上那么共享内存与管道通信有什么区别? system v消息队列 system v信号…

浅谈 Linux fork 函数

文章目录 前言fork 基本概念代码演示示例1:体会 fork 函数返回值的作用示例2:创建多进程,加深对 fork 函数的理解 前言 本篇介绍 fork 函数。 fork 基本概念 pid_t fork(void) fork 的英文含义是"分叉",在这里就是 …

web安全学习笔记【15】——信息打点(5)

信息打点-CDN绕过&业务部署&漏洞回链&接口探针&全网扫描&反向邮件 #知识点: 1、业务资产-应用类型分类 2、Web单域名获取-接口查询 3、Web子域名获取-解析枚举 4、Web架构资产-平台指纹识别 ------------------------------------ 1、开源-CMS指…

非线性优化资料整理

做课题看了一些非线性优化的资料,整理一下,以方便查看: 优化的中文博客 数值优化|笔记整理(8)——带约束优化:引入,梯度投影法 (附代码)QP求解器对比对于MPC的QP求解器 数值优化| 二次规划的…

day02_前后端环境搭建(前端工程搭建,登录功能说明,后端项目搭建)

文章目录 1. 软件开发介绍1.1 软件开发流程1.2 角色分工1.3 软件环境1.4 系统的分类 2. 尚品甄选项目介绍2.1 电商基本概念2.1.1 电商简介2.1.2 电商模式B2BB2CB2B2CC2BC2CO2O 2.2 业务功能介绍2.3 系统架构介绍2.4 前后端分离开发 3. 前端工程搭建3.1 Element-Admin简介3.2 El…

漫漫数学之旅034

文章目录 经典格言数学习题古今评注名人小传 - 大卫希尔伯特 经典格言 研究数学的艺术在于发现包含普遍性萌芽的特殊情形。——大卫希尔伯特(David Hilbert) 亲爱的朋友,让我们一起进入数学的奇幻世界,那里大卫希尔伯特就像一位智…

LightSNS V1.6.6.0版轻社区解锁版源码优化版

优化:后台面板首页数据统计改成异步加载 优化:同一内容重复评论提示 优化:vip到期个人主页vip专属背景还保留问题 优化:活动报名名额为空可能导致的问题 优化:移动端评论框左下“转发动态”改为“转发内容” 优化&…

【C++】树形关联式容器set、multiset、map和multimap的介绍与使用

👀樊梓慕:个人主页 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 🌝每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.关联式容器 2.键…