JavaEE——常见的锁策略、CAS、synchronized 原理(八股)

文章目录

  • 一、常见的锁策略
    • 1.乐观锁 & 悲观锁
    • 2.轻量级锁 & 重量级锁
    • 3.自旋锁 & 挂起等待锁
    • 4.互斥锁 & 读写锁
    • 5. 公平锁 & 非公平锁
  • 二、CAS
    • 1、什么是 CAS
    • 2. CAS 的应用场景
    • 2.实现自旋锁
    • 3. CAS 中的 ABA 问题
  • 三、 Synchronized 原理

一、常见的锁策略

当前此处介绍的这些所策略不仅仅局限于 Java,任何与锁相关的特性都与这些相关。

1.乐观锁 & 悲观锁

这里要注意的是,此处说的锁并不是单指一把锁,而是一类锁。

  • 乐观锁: 通常假设不会发生冲突,只有在数据提交更新时才会对冲突进行检测,如有冲突就会返回信息,交给用户处理。
  • 悲观锁: 通常假设最坏的情况,冲突一直存在,认为每次获取数据是都会有人修改,所以每次获取数据时都会加锁。

总的来讲,就是两者对锁的竞争激烈程度的认知不同。

synchronized 既是一个悲观锁,又是一个乐观锁。
默认是乐观锁,但是发现所竞争比较激烈时,就会变成悲观锁。

2.轻量级锁 & 重量级锁

  • 轻量级锁: 轻量级加锁的开销较小,效率更高。(进行了少量内核态用户切换)
  • 重量级锁: 重量级加锁的开销较大,效率更低。(进行了大量内核态用户切换)

这里说明一个比较常见的情况(多数情况下):
多数情况下,乐观锁,是一个轻量级锁。
悲观锁,是一个重量级锁。

synchronized 既是一个轻量级锁,又是一个重量级锁。
默认是轻量级锁,当锁竞争比较激烈时,就会转换为重量级锁。

3.自旋锁 & 挂起等待锁

首先说明一下这两类锁的特性;
自旋锁,是一种典型的轻量级锁。
挂起等待锁,是一种典型的重量级锁。

下面我通过举例来解释两类锁的特点:
首先设想一个场景,这里我们邀请三为人选,分别是: 喜羊羊,美羊羊,沸羊羊

此时,沸羊羊 向 美羊羊 表白(尝试对美羊羊加锁),但是,美羊羊说,沸羊羊你是个好人,但是我有男朋友了(说明此时 喜羊羊 对美羊羊已经加锁)

呢么,沸羊羊想要上位,就只能等待(锁释放),于是有下面两种。

  • 针对自旋锁: 每天都去问候美羊羊,时刻关心她,一但她和喜羊羊分手,可以第一时间得知。快速尝试获取锁,并建立关系。
    很明显,自旋锁的形式,占用了大量的资源。
  • 针对挂起等待锁: 沸羊羊被婉拒后,暗下决心,表示我愿意等,只要你幸福开心,我就躲起来不打扰你,如果哪一天想起我,你就告诉我。
    这样就非常不确定,当 美羊羊 分手了,可能想起来沸羊羊,求安慰。但是,大概率根本不会想到。
    所以对于挂起等待锁,当真正被唤醒的时候就可能已经沧海桑田了。但是优点也就是相对节省资源。

synchronized 这里的轻量级锁,是基于自旋锁的形式实现。
synchronized 这里的重量级锁,是基于挂起等待锁实现的。

4.互斥锁 & 读写锁

  • 互斥锁: 就是提供 加锁 和 解锁 两个操作。当一个线程加锁了,另一个线程也尝试加锁就会阻塞等待。
  • 读写锁: 提供三种操作
    1.针对读加锁
    2.针对写加锁
    3.解锁
    上面的三种操作,针对的是多线程对同一个变量并发写操作,读操作没有线程安全问题。
    即就是,读锁与读锁之间没有互斥,写锁与写锁之间存在互斥,写锁与读锁之间存在互斥

synchronized 不是读写锁

5. 公平锁 & 非公平锁

在这里,我们将公平定义为先来后到

同样,这里可以设定一个场景,此时假设美羊羊分手了。

公平锁: 当美羊羊分手后,由等待队列中最早的舔狗上位。(阻塞队列中的元素根据顺序依次获取锁)
在这里插入图片描述

  • 非公平锁: 三个人都不等了,开始上去争抢,各凭本事。(阻塞队列中的元素竞争获取锁)
    在这里插入图片描述

synchronized 是非公平锁。

二、CAS

1、什么是 CAS

CAS:全称为Compare and swap,字面意思: 比较并交换

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

  1. 比较 A 和 V 的值是否相同。(比较)
  2. 若比较的值相同,将 B 写入 V。(交换)
  3. 返回操作是否成功。

如图所示:
在这里插入图片描述
这里需要注意的是,CAS 的这个过程,并非是一段代码实现,而是通过 一条 CPU 指令实现。
也就是说 CAS 操作是原子性的,这样就可以在一定程度上回避线程安全问题。

2. CAS 的应用场景

  1. 实现原子类

因为是原子类,真实的 CAS 是一个原子硬件指令来完成的,实现的是 i++ 这样的操作,这里只能使用伪代码来辅助理解,如图:
在这里插入图片描述
在这里插入图片描述
如上图所示,设定两个线程分别实现自增。
在这里插入图片描述
此时假设线程1 优先抢占CPU
在这里插入图片描述
此时,线程2进入 CPU
在这里插入图片描述
最后返回线程各自的 oldvalue 即可!

总的来说,CAS 就是 CPU 提供给我们的一种特殊指令,通过这个指令,可以再一定程度上处理线程安全问题。

2.实现自旋锁

同样,自旋锁也是以伪代码的形式展现。如图所示:
在这里插入图片描述

3. CAS 中的 ABA 问题

什么是 ABA 问题

我们已知,CAS 在运行中,就是检查 value 和 oldvalue 是否一致。如果一致,就视为 value 的值中途没有被修改过,所以下一步交换没有问题。

需要注意的是这里的 “一致”,可能是没有改过,也可能是 改过,但是又还原回来了。

通俗来讲,就是,我买了个手机,这个手机可能是新机,也可能是翻新机这里我们不专业,无法区分!

ABA 这样的问题,在大部分情况下影响都不大。但是,仍然有极端情况不容忽视,问题如下:
假设到 ATM 上取钱,在取钱的时候,按下取钱键的一瞬间,机器故障卡了,此时我又不耐烦的多按了几下,此时就可能产生 bug,造成重复扣款的情况,如图:

在这里插入图片描述

解决方案

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

  • CAS 操作在读取旧值的同时, 也要读取版本号.
  • 真正修改的时候,
    1.如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
    2.如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).

三、 Synchronized 原理

目前,我们知道 synchronized 的最基本用法是,两个线程针对同一个对象加锁,会产生阻塞等待。
对于 synchronized 内部,其实还有很多优化机制,存在的目的就是为了让锁更高效。

  1. 锁升级/锁膨胀
    当代码执行到 synchronized 代码块中实现加锁可能会经历下面几个过程,如图:
    在这里插入图片描述
  • 偏向锁: 进行加锁时,首先会进入到偏向锁这个状态。
    这里并不是真正的加锁,而是先占一个位置,如有需要就真加锁,无需要就放弃。
  • 轻量级锁: 当发生锁竞争的时候,就会从偏向锁升级为轻量级锁
    此时 synchronized 会通过 自旋 的方式进行加锁。
  • 重量级锁: 在 synchronized 进行自旋时,内部有个计数器,当自旋到一定时间后,就会自动升级成重量级锁。
    此时锁的类型是 挂起等待锁 ,是基于操作系统原生的 API 进行加锁了。

呢么有个问题,尽然能实现锁升级,呢么可不可以降级?
答案是不行。 在 JVM 的主流实现中,只有锁升级,没有锁降级。只要是锁对象,一旦被升级,就不能再回头了。

  1. 消除锁
    编译器的智能判定,看当前的代码是否真的需要加锁,如果不需要,但是程序员加了,就自动将锁消除。
  2. 锁粗化
    锁的粒度:synchronized 所包含的代码越多,粒度就越粗,包含的越少,粒度就越细。
    通常情况下,一般认为锁的粒度细一点较好。

对于这里的内容,本人整理的十分有限,不足的地方还希望大家多多指点。

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

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

相关文章

MySQL数据库基础

目录 为什么使用数据库? 主流数据库 数据库基本使用 连接服务器 服务器管理 理解服务器、数据库、表的关系 使用示例 MySQL架构 MySQL架构分层 MySQL架构的主要组件: SQL分类 存储引擎 为什么使用数据库? 我们平时存储数据常用各…

简要介绍 | 融合深度学习与符号逻辑:神经符号结合的探索

注1:本文系“简要介绍”系列之一,仅从概念上对神经符号结合进行非常简要的介绍,不适合用于深入和详细的了解。 融合深度学习与符号逻辑:神经符号结合的探索 Neuro-Symbolic AI 本文将探讨神经符号结合的概念、原理、研究现状、挑战…

电脑应用程序发生异常怎么办?

有时候我们打开电脑上面的某个软件时,会打不开,并且会弹出如下的错误提示“应用程序发生异常 未知的软件异常(xxx),位置为xx”。相信大多数的人在使用电脑的时…

ThreadLocal与InheritableThreadLocal及线程池的影响

在web开发中使用了ThreadLocal本地线程存储拦截器解析的用户信息,方便在下文代码中调用,但是在springboot中使用Async开启异步操作时,就会造成,子线程无法拿到父本地线程数据。拿到一些脏数据。 1.InheritableThreadLocal 在这个…

Linux 批量杀掉进程(包含某个关键字)

一、场景说明 现场环境有十多个包含 ”celery” 关键字的进程在运行,每次重启服务,需要将这些进行kill掉,然后重新启动。 可以用如下命令批量kill掉这些进程: kill -9 PID1 PID2 PID3 PID4.....其中,PID是查询到的进…

Node.js开发

Node.js是一个基于V8 JavaScript引擎的JavaScript运行时环境。 也就是说Node.js基于V8引擎来执行JavaScript的代码,但是不仅仅只有V8引擎:  前面我们知道V8可以嵌入到任何C 应用程序中,无论是Chrome还是Node.js,事实上都是嵌入…

Docker NGINX 加载Geoip模板

前提环境: Docker 环境 涉及参考文档: ngx_http_geoip_module 模块Loki NGINX Service MeshGeoIP IP库 一、下载GeoIP IP库 二、配置Nginx主配置文件 vim /data/nginx/MangoMoh/dos/nginx.confuser nginx; worker_processes auto;error_log /var…

postman每次请求前获取token并保存到环境变量

文章目录 创建一个tab,写上接口地址,json类型的参数,点击send,返回成功。 点击Environment,新建dev环境,创建token变量 编写Tests脚本 // 获取响应的token值 var responseJson pm.response?.json()…

【Redis】特殊数据类型 Geo (地理位置)

🎯前言 除了五中基本的数据类型外,Redis还支持两种特殊的数据类型,第一种 Geo (地理位置):用于存储地理位置相关的数据,例如经纬度、距离等。第二种 Stream (流):是一个高级的列表类型,支持对列…

关于c/c++中的isdigit()函数(判断一个字符是不是数字字符)

1&#xff1a;做用&#xff1a;判断一个字符是不是数字字符&#xff08;即&#xff1a;相当于&#xff1a;s[i]>0&&s[i]<9&#xff09; 2&#xff1a;使用方式 char cA; string s"123fgv"; if(isdigit(c)); if(isdigit(s[i]))//返回bool类型 3&…

What Is the Character Limit for ChatGPT? 聊天GPT的字符限制是多少?

The character limit for ChatGPT text prompts is 4096 characters, and there’s also a limit of 4096 tokens per conversation. If you aren’t sure how many tokens your prompt uses, you can calculate that with OpenAI’s Tokenizer tool. ChatGPT 文本提示的字符限…

每周学点数学 3:概率论基础2

文章目录 1.独立性与相关性2.条件概率与边缘概率3.大数定律与中心极限定理4.随机过程5.概率论的应用 1.独立性与相关性 独立性与相关性是在数据分析中非常重要的两个概念&#xff0c;它们之间存在一定的联系&#xff0c;但也有明显的区别。 独立性&#xff08;Independence&…