当前位置: 首页 > news >正文

【JavaEE初阶】第一节.多线程(进阶篇 ) 常见的锁策略、CAS及它的ABA问题

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、常见的锁策略
  •       1.1 乐观锁 vs 悲观锁
  •       1.2 普通的互斥锁 vs 读写锁
  •       1.3 重量级锁 vs 轻量级锁
  •       1.4 自旋锁 vs 挂起等待锁
  •       1.5 公平锁 vs 非公平锁
  •       1.6 可重入锁 vs 不可重入锁
  • 二、CAS
  •       2.1 CAS典型应用场景
  •              2.1.1 使用CAS实现原子类
  •              2.1.2 使用CAS实现自旋锁
  •       2.2 CAS中的ABA问题(小概率bug)
  •             2.2.1 什么是ABA问题
  •             2.2.2 ABA问题引发的bug
  •             2.2.3 解决ABA问题的办法
  • 总结


前言

前几节内容我们介绍了有关多线程基础的有关内容,今天开始我们将进入到多线程的进阶的学习当中;接下来的内容会有很大的难度;希望各位能够认真学习,争取能够很好的掌握今天将要学习的内容;

进阶篇中的很多知识,不再是工作中常用的,但是却是在面试中常考的(俗称:面试造核弹,工作拧螺丝)


提示:以下是本篇文章正文内容,下面案例可供参考

一、常见的锁策略

简单通俗的来说,锁策略就是 加锁的时候是咋加的

1.1 乐观锁 vs 悲观锁

乐观锁:预测接下来锁冲突的概率不大,就会少做一点工作,成本更小;

悲观锁:预测接下来锁冲突的概率很大,就会多做一点工作,成本更大;

举例说明:

比如说,就前段时间,西安那边又有确诊的了;

有居民就比较紧张,就在想是不是要在家里屯点菜啥的(疫情会引起封城,封城会影响买菜),提前屯点菜以备不时之需

这个就可以看作是 悲观锁,花费所需成本较大(买菜、运菜、放在地上......);

当时有居民却认为,由于之前已经有过几次确诊的经历,所以说 已经有了不少经验了,所以封城的概率比较小,不需要提前屯菜(屯了吃不完大概率会坏)

这个就可以看作是 乐观锁,话费所需成本更小;

synchronized

就既是一个悲观锁,也是一个乐观锁,准确的来说 它是一个自适应锁;

如果当前锁冲突概率不大,就以乐观锁的方式运行,往往是纯用户态执行的;

一旦发现锁冲突概率大了,就以悲观锁的方式运行,往往要进入内核,对当前线程进行挂起等待;

1.2 普通的互斥锁 vs 读写锁

synchronized 就属于普通的互斥锁,两个加锁操作之间会发生竞争;

读写锁,把加锁操作细化了 "加读锁" "加写锁";


情况一:

线程A 尝试加写锁,线程B 尝试加写锁,此时 A和B 产生竞争,和普通的锁没有区别;


情况二:

线程A 尝试加读锁,线程B 尝试加读锁,此时 A和B 不产生竞争,和没有加锁一样(多线程读,不涉及修改,是线程安全的)

这种情况是相当普遍的;


情况三:

线程A 尝试加读锁,线程B 尝试加写锁,此时 A和B 不产生竞争(一个读一个写所以不存在竞争),和普通的锁没有区别;

1.3 重量级锁 vs 轻量级锁
 

重量级锁:锁开销比较大,做的工作比较多;

轻量级锁:锁开销比较小,做的工作比较小;

重量级锁、轻量级锁 与之前所介绍的 乐观锁、悲观锁 差不多(内容上不是完全的区分开),但是最终的着力点还是不一样的

其中,在大部分情况下(不绝对),悲观锁 经常会是重量级锁,乐观锁 经常会是轻量级锁

重量级锁 主要依赖了操作系统提供的锁,使用这种锁,容易产生阻塞等待;

轻量级锁 主要尽量的避免使用操作系统提供的锁,尽量在用户态完成功能,尽量避免用户态和内核态的切换,尽量避免挂起等待;

同时,synchronized 是一个自适应锁,既是轻量级锁,也是重量级锁;

锁冲突不高:轻量级;

锁冲突很高:重量级;

1.4 自旋锁 vs 挂起等待锁

自旋锁 是轻量级锁的具体实现,挂起等待锁 是重量级锁的具体实现;

自旋锁:当发生锁冲突的时候,不会挂起等待,会迅速来尝试看这个锁能不能获取到(更轻量,乐观锁)

特点:

  1. 一旦锁被释放,就可以第一时间获取到;
  2. 如果锁一直不释放,就会消耗大量的;

可以看作是一个 不断的循环,可以用一个伪代码来表示:

//自旋锁伪代码,不停的循环
while(抢锁(lock) == 失败) {}

挂起等待锁:发现锁冲突,就挂起等待(更重量,悲观锁)

特点:

  1. 一旦锁被释放,不能第一时间获取到;
  2. 在锁被其他线程占用的时候,会放弃CPU资源;

总结:

synchronized 作为轻量级锁的时候,内部是 自旋锁作为重量级锁的时候,内部是 挂起等待锁

1.5 公平锁 vs 非公平锁

啥样的情况才算是公平?

一般认为,符合 "先来后到" 这样的规则,就是公平!!!

公平锁:多个线程等待一把锁的时候,谁先来尝试拿着一把锁,这把锁就是谁的;

非公平锁:多个线程等待一把锁的时候,就和哪个线程先来后到没有关系,每个线程拿到锁的概率是均等的;

synchronized 是非公平锁;

1.6 可重入锁 vs 不可重入锁

一个线程连续加锁两次,不会造成死锁,那么这个锁就叫做 可重入锁

一个线程连续加锁两次,会造成死锁,那么这个锁就叫做 不可重入锁

 代码示例:

  private  static void func() {//......进行一些多线程操作//第一次加锁synchronized (Demo27.class) {//第二次加锁synchronized (Demo27.class) {}}}

如上述代码,第一次加锁能够成功,Demo27.class 处于被加锁的状态;但是 第二次加锁,由于 Demo27.class 已经是被加锁的状态了,所以就会呈现出 阻塞状态;

要等待第一次加锁释放掉,第二次加锁才能够成功;但是 要想第一次加锁释放,那么 又必须要到第二次加锁成功之后,代码往下执行 .

这样就构成了一个死循环,就叫做 死锁!!!

 synchronized 属于可重入锁;

 

二、CAS

CAS 是操作系统硬件 给JVM提供的另外一种更轻量的原子操作的机制

准确来说,CAS是CPU提供的一条特殊的指令 —— compare and swap(比较和交换);

CAS 是一个原子指令;

比较:是比较内存和寄存器的值;

如果相等,则把寄存器和另一个值进行交换;如果不相等,就不进行操作;

代码实现:

//CAS 的伪代码来理解它的工作流程
//其中,address表示内存地址,expextValue表示一个寄存器中 用来比较的值,
//expextValue表示另一个寄存器中 用来交换的值
boolean CAS(address,expextValue,swapValue) {if(&address == expextValue) {&address = swapValue;return true;}return false;
}
//上面一系列操作都是由一个CPU指令来完成的

2.1 CAS典型应用场景

2.1.1 使用CAS实现原子类

原子类:这是标准库中提供的一组类,可以让原子的进行 ++、-- 等运算 ;

代码实现:

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

在之前,我们已经介绍过,最终的结果不是 10_0000 ;

运行结果:


我们可以使用加锁来解决这个问题,也可以使用原子类来解决这个问题: 

package thread;import java.util.concurrent.atomic.AtomicInteger;public class Demo28 {//public static int count = 0;public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {//count++;//这个方法相当于count++count.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {//count++;count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);}
}
//和之前的不同的代码已注释,这是使用 原子类来解决问题的,没有使用加锁操作,也实现了线程安全

运行结果:


在Java标准库 里面提供了基于CAS所实现的 "原子类",是线程安全的;

这些 "原子类" 通常以 Atomic 开头,对常用的 int、long等等 进行了封装,如:

2.1.2 使用CAS实现自旋锁 

代码实现:

//自旋锁伪代码
public class SpinLock {private Thread owner = null;public void lock() {//当前的owner是否为空,为空即为当前没有加锁,于是就进行交换,//把当前要给加锁的线程的值赋予owner//非空就不去进行交换,就循环继续进行,呈现自旋的状态while(!CAS(this.owner,null,Thread.currentThread())) {}}
}

当 owner 为 null 的时候 CAS 才能成功,循环才能结束;

当 owner 为非null,这说明当前的锁已经被其他线程给占用了,因此 就需要继续循环(自旋);

2.2 CAS中的ABA问题(小概率bug) 

2.2.1 什么是ABA问题

ABA问题可以单纯的这样理解:如果你去买一个手机,那么你无法区分 它是一个新机,还是一个翻新机(二手的、外面包装和新机一样);

类似的,在CAS里面,也无法区分,数据始终就是A;还是数据从 A 变成 B,之后又变回了 A ;

如果是前者,那么一点问题都没有;但是如果是后者,那么 CAS 就会有一定的概率引发 bug(极端情况下的小概率事件) 

图示示例:

 2.2.2 ABA问题引发的bug

这里结合一个具体的例子,来介绍ABA问题引发的bug;

举例说明;

假设滑稽老铁有 1000 存款,此时想要从 ATM机 上取走 500(ATM机 按照CAS的方式来进行操作)

取钱的时候,按下取款按钮,就会触发一个 "取钱的线程",但是 滑稽老铁手一滑,连续按了两下(即 产生了两个线程)


但是,怕就怕在这期间 突然又来了一个线程(比如说 滑稽老铁的一个朋友,此时正好向滑稽老铁转了500)

这时候,就扣除了两次钱了,这个就是典型的ABA问题(极端情况下的小概率问题);

此时,线程2不知道 当前的1000,始终是1000;还是 1000 -> 500 -> 1000 ;

2.2.3 解决ABA问题的办法

正经的解决ABA问题的办法,是想办法获取到中间过程 —— 引入一个 "版本号" 来解决 ;

在上述的例子当中,CAS是比较的是 余额,余额相同,就可以进行修改(余额是可以变大和变小,所以就会出现ABA问题)

但是,如果换成 "版本号",并且规定 "版本号" 只能增不能减,那么就不会出现ABA问题;

当然,解决ABA问题的办法肯定不止这一种,这里只是列举了一种非常典型的办法 ;

 总结

好了,这篇博客到这里就已经结束了

本篇博客主要介绍的是 各种常见的锁策略,以及CAS、CAS中的小概率bug —— ABA问题,并且介绍了ABA问题的解决方案 ;

 

相关文章:

【JavaEE初阶】第一节.多线程(进阶篇 ) 常见的锁策略、CAS及它的ABA问题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、常见的锁策略 1.1 乐观锁 vs 悲观锁 1.2 普通的互斥锁 vs 读写锁 1.3 重量级锁 vs 轻量级锁 1.4 自旋锁 vs 挂起等待锁 1.5 公平…...

Linux基础命令-pstree树状显示进程信息

Linux基础命令-uname显示系统内核信息 Linux基础命令-lsof查看进程打开的文件 Linux基础命令-uptime查看系统负载 文章目录 前言 一 命令介绍 二 语法及参数 2.1 使用man查看命令语法 2.2 常用参数 三 参考实例 3.1 以树状图的形式显示所有进程 3.2 以树状图显示进程号…...

keepalived+LVS配置详解

keepalivedLVS配置详解keepalived简介keepalived的应用场景keepalived工作原理VRRP协议核心组件分层工作工作状态LVS简介LVS三种模式NAT模式(网络地址映射)IPTUN模式(IP隧道)DR模式(直接路由)三种模式对比keepalivedLVS配置1.master配置2. keepalived配置文件3 修改keepalived配…...

Unity之C#端使用protobuf

什么是protobuf protobuf全称Protocol Buffers&#xff0c;由Google推出的一种平台、语言无关的数据交互格式&#xff0c;目前使用最广泛的一种数据格式&#xff0c;尤其在网络传输过程中&#xff0c;有很强的安全性&#xff0c;而且数据量比json和xml要小很多。 最主要的是pr…...

C++设计模式(18)——模板方法模式

亦称&#xff1a; Template Method 意图 模板方法模式是一种行为设计模式&#xff0c; 它在超类中定义了一个算法的框架&#xff0c; 允许子类在不修改结构的情况下重写算法的特定步骤。 问题 假如你正在开发一款分析公司文档的数据挖掘程序。 用户需要向程序输入各种格式…...

SQLserver 索引碎片

Oracle 不需要整理碎片&#xff0c;原因&#xff1f; 1. rowid 默认的索引是&#xff22;&#xff0d;树索引。索引建立在表中的一个或多个列或者是表的表达式上&#xff0c;将列值和行编号一起存储。行编号是唯一标记表中行的伪列。 行编号是物理表中的行数据的内部地址&am…...

【Storm】【二】安装

1 准备 1.1 准备linux服务器 本文搭建的是3节点的集群&#xff0c;需要3台linux服务器&#xff0c;我这里使用的是centos7版本的linux虚拟机&#xff0c;虚拟机网络配置如下&#xff1a; 主节点&#xff1a; master 192.168.92.90 从节点&#xff1a; slave1 192.168.92.…...

Android ConditionVariable

Android ConditionVariable 线程操作经常用到wait和notify&#xff0c;用起来稍显繁琐&#xff0c;而Android给我们封装好了一个ConditionVariable类&#xff0c;用于线程同步。提供了三个方法block()、open()、close()。 void block() //阻塞当前线程&#xff0c;直到条件为…...

Action Segmentation数据集介绍——Breakfast

文章目录简介细节Cooking actibitiesillustration of the actions论文讲解Breakfast&#xff08;The Breakfast Action Dataset&#xff09;简介 早餐动作数据集包括与早餐准备相关的10个动作&#xff0c;由18个不同厨房的52个不同的人执行。该数据集是最大的完全带注释的数据…...

横道图时间标尺在P6软件中的设置

卷首语 由于其直观简洁且易于管理的特性&#xff0c;使其成为展示项目活动顺序及时间安排的最常用的进度管理工具。 甘特图 甘特图&#xff08;Gantt Chart&#xff09;&#xff0c;又称为横道图或棒条图&#xff0c;是最早的项目进度管理工具之一。由于其直观简洁且易于管理…...

空间复杂度(超详解+例题)

全文目录引言空间复杂度例题test1test2&#xff08;冒泡排序&#xff09;test3&#xff08;求阶乘&#xff09;test4&#xff08;斐波那契数列&#xff09;总结引言 在上一篇文章中&#xff0c;我们提到判断一个算法的好坏的标准是时间复杂度与空间复杂度。 时间复杂度的作用…...

Document-Level event Extraction via human-like reading process 论文解读

Document-Level event Extraction via human-like reading process 论文&#xff1a;2202.03092v1.pdf (arxiv.org) 代码&#xff1a;无 期刊/会议&#xff1a;ICASSP 2022 摘要 文档级事件抽取(DEE)特别困难&#xff0c;因为它提出了两个挑战:论元分散和多事件。第一个挑战…...

H5盲盒抽奖系统源码

盲盒抽奖系统4.0&#xff0c;带推广二维码防洪炮灰功能和教程。 支持微信无限回调登录 标价就是源码价格&#xff0c;vuetp5框架编写&#xff0c;H5网页&#xff0c;前后端分离 此源码为正规开发&#xff0c;正版产品已申请软著。 开源无加密无授权&#xff0c;可以二开使用…...

低代码平台和无代码平台哪个更适合开发企业管理系统?

编者按&#xff1a;本文分析了开发企业管理系统所需要的平台特性&#xff0c;并根据这些特点和低代码无代码的优劣比较&#xff0c;得出低代码平台更适合开发企业管理系统。关键词&#xff1a;私有化部署&#xff0c;可视化设计&#xff0c;源码交付&#xff0c;数据集成&#…...

75岁彪马再发NFT 复活美洲狮IP

在“运动品牌Web3”的潮流里&#xff0c;彪马&#xff08;PUMA&#xff09;绝对算是发烧友级别。2月22日&#xff0c;这家德国服装品牌的新NFT又来了&#xff0c;总量10000个Super PUMA NFT中&#xff0c;将有4000个以0.15 ETH&#xff08;约为255美元&#xff09;价格正式公售…...

大学生成人插画培训机构盘点

成人插画培训机构哪个好&#xff0c;成人学插画如何选培训班&#xff1f;给大家梳理了国内较好的插画培训机构排名&#xff0c;各有优势和特色&#xff0c;供大家参考&#xff01; 一&#xff1a;国内成人插画培训机构排名 1、轻微课&#xff08;五颗星&#xff09; 主打课程有…...

【算法基础】一维差分 + 二维差分

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;【C/C】算法 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵 希望大佬指点一二 如果文章对你有…...

游戏服务器框架 技能buff篇

游戏服务器框架 技能buff篇 1.状态 state 全局API 用于定义各种状态检查 bool IsDead(){ // 死亡buff if (buff->id 10001){ return true; } return false; } bool IsInvincible(){ if (buff->id 20001 || buff->id 20002){…...

网友说socket通信讲的不彻底,原来这才是Socket

关于对 Socket 的认识&#xff0c;大致分为下面几个主题&#xff0c;Socket 是什么&#xff0c;Socket 是如何创建的&#xff0c;Socket 是如何连接并收发数据的&#xff0c;Socket 套接字的删除等。 Socket 是什么以及创建过程 一个数据包经由应用程序产生&#xff0c;进入到…...

Nginx第二讲

目录 二、Nginx02 2.1 keepalived和heartbeat介绍 2.1.1 两者的介绍 2.1.2 keepalived简介 2.1.3 VRRP协议与工作原理 2.1.4 Keepalvied的工作原理 2.2 安装环境及keepalived 2.3 启动与验证keepalived 2.4 keepalived测试 2.4.1 环境准备 2.4.2 配置keepalived 2.…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

软件工程 期末复习

瀑布模型&#xff1a;计划 螺旋模型&#xff1a;风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合&#xff1a;模块内部功能紧密 模块之间依赖程度小 高内聚&#xff1a;指的是一个模块内部的功能应该紧密相关。换句话说&#xff0c;一个模块应当只实现单一的功能…...

恶补电源:1.电桥

一、元器件的选择 搜索并选择电桥&#xff0c;再multisim中选择FWB&#xff0c;就有各种型号的电桥: 电桥是用来干嘛的呢&#xff1f; 它是一个由四个二极管搭成的“桥梁”形状的电路&#xff0c;用来把交流电&#xff08;AC&#xff09;变成直流电&#xff08;DC&#xff09;。…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下&#xff0c;音视频内容犹如璀璨繁星&#xff0c;点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频&#xff0c;到在线课堂中知识渊博的专家授课&#xff0c;再到影视平台上扣人心弦的高清大片&#xff0c;音…...

前端调试HTTP状态码

1xx&#xff08;信息类状态码&#xff09; 这类状态码表示临时响应&#xff0c;需要客户端继续处理请求。 100 Continue 服务器已收到请求的初始部分&#xff0c;客户端应继续发送剩余部分。 2xx&#xff08;成功类状态码&#xff09; 表示请求已成功被服务器接收、理解并处…...