多线程篇(基本认识 - 锁机制 - 乐观锁 悲观锁)(持续更新迭代)

一、Java层

乐观锁(CAS)和悲观锁(synchronized)

1. 锁

在代码中多个线程需要同时操作共享变量,这时需要给变量上把锁,保证变量值是线程安全的。

锁的种类非常多,比如:互斥锁、自旋锁、重入锁、读写锁、行锁、表锁等这些概念,总结下来

就两种类型,乐观锁和悲观锁。

2. 乐观锁(Optimistic Locking)

乐观锁就是持比较乐观态度的锁。在操作数据时非常乐观,认为别的线程不会同时修改数据,只

有到数据提交的时候才通过一种机制来验证数据是否存在冲突。一般使用CAS算法实现。乐观锁

适用于多读的应用类型,这样可以提高吞吐量。

3. 悲观锁(Pessimistic Concurrency Control)

比较悲观的锁,总是想着最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数

据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

在 Java 中,synchronized从偏向锁、轻量级锁到重量级锁,全是悲观锁。

JDK提供的Lock实现类全是悲观锁。一般用于多写的场景。

4. CAS

CAS 即 Compare and Swap,它体现的一种乐观锁的思想,

比如:多个线程要对一个共享的整型变量执行 +1 操作:

// 需要不断尝试
while (true) {int 旧值 = 共享变量; // 比如拿到了当前值 0int 结果 = 旧值 + 1; // 在旧值 0 的基础上增加 1 ,正确结果是 1/*这时候如果别的线程把共享变量改成了 5,本线程的正确结果 1 就作废了,这时候compareAndSwap 返回 false,重新尝试,直到:compareAndSwap 返回 true,表示本线程做修改的同时,别的线程没有干扰*/if( compareAndSwap ( 旧值, 结果 )) {// 成功,退出循环}
}

获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。

结合 CAS 和 volatile 可以实现无锁并发,适用于竞争不激烈、多核 CPU 的场景下

CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令。

java.util.concurrent中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、

AtomicBoolean等,它们底层就是采用 CAS 技术 + volatile 来实现的。

java的内存模型(可见性,原子性,有序性)详细介绍_傻鱼爱编程的博客-CSDN博客这个里面原

子性问题,用CAS技术解决方案如下:

    // 创建原子整数对象private static AtomicInteger i = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int j = 0; j < 1000; j++) {i.getAndIncrement();  // 获取并且自增  i++}});Thread t2 = new Thread(() -> {for (int j = 0; j < 1000; j++) {i.getAndDecrement(); // 获取并且自减  i--}});t1.start();t2.start();t1.join();t2.join();System.out.println(i);}

5. synchronized

Java HotSpot 虚拟机中,每个对象都有对象头(包括 class 指针和 Mark Word)。

Mark Word 平时存储这个对象的哈希码 、分代年龄,当加锁时,这些信息就根据情况被替换为

标记位 、 线程锁记录指针 、重量级锁指针 、线程ID 等内容。

JDK5引入了CAS原子操作,从JDK6开始对synchronized的实现机制进行了各种优化,包括使用

JDK5引进的CAS自旋之外,还增加了自适应的CAS自旋、锁消除、锁粗化、偏向锁、轻量级锁

(默认开启偏向锁)这些优化策略。由于此关键字的优化使得性能极大提高,同时语义清晰、操作

简单、无需手动关闭,推荐在允许的情况下尽量使用此关键字。

JDK6以后锁主要存在四种状态,依次是:无锁状态(对象头中存储01)、偏向锁状态(对象头中存

储线程id)、轻量级锁状态(对象头中存储00)、重量级锁状态(对象头中存储10),锁的升级是单向

偏向锁

Java6 中引入了偏向锁来做优化:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线

程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就

表示没有竞争,不用重新 CAS。

偏向锁的缺点:

  1. 撤销偏向需要将持锁线程升级为轻量级锁,这个过程中所有线程需要暂停(STW)
  2. 访问对象的 hashCode 也会撤销偏向锁
  3. 撤销偏向和重偏向都是批量进行的,以类为单位,如果撤销偏向到达某个阈值,整个类的所有对象都会变为不可偏向的

开启偏向锁(默认开启):-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

关闭偏向锁:-XX:-UseBiasedLocking

举个例子分析一下:

假设有两个方法同步块,利用同一个对象加锁

Object ob = new Object();
public void method1() {synchronized(ob) {// 同步块 Amethod2();}
}
public void method2() {synchronized(ob) {// 同步块 B}
}

解释过程如下:

轻量级锁

若偏向锁失败,它会尝试使用轻量级锁的优化手段,

此时Mark Word 的结构也变为轻量级锁的结构。

如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),

如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

轻量级锁竞争的时候,用自旋进行了优化,如果当前线程自旋成功(即这时候持锁线程已经退出

了同步块,释放了锁),这时当前线程就可以避免阻塞。在Java 6之后自旋锁是自适应的,比如

对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,

就少自旋甚至不自旋,总之,比较智能。自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多

核 CPU 自旋才能发挥优势。

Java 7 之后不能控制是否开启自旋功能。

举个例子分析一下:

假设有两个方法同步块,利用同一个对象加锁

Object ob = new Object();
public void method1() {synchronized(ob) {// 同步块 Amethod2();}
}
public void method2() {synchronized(ob) {// 同步块 B}
}

解释过程如下: 每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的 Mark

Word

重量锁

如果在尝试加轻量级锁的过程中,CAS 操作无法成功(经过自旋),这时一种情况就是有其它

线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。

二、数据库层

1. 锁(Lock)

在介绍悲观锁和乐观锁之前,让我们看一下锁。

锁,在我们生活中随处可见,我们的门上有锁,我们存钱的保险柜上有锁,是用来保护我们财产

安全的。

程序中也有锁,当多个线程修改共享变量时,我们可以给修改操作上锁(syncronized)。

当多个用户修改表中同一数据时,我们可以给该行数据上锁(行锁)。

因此,锁其实是在并发下控制多个操作的顺序执行,以此来保证数据安全的变动。

并且,锁是一种保证数据安全的机制和手段,而并不是特定于某项技术的。

2. 悲观锁(Pessimistic Concurrency Control)

第一眼看到它,相信每个人都会想到这是一个悲观的锁。

没错,它就是一个悲观的锁。那这个悲观体现在什么地方呢?

悲观是我们人类一种消极的情绪,对应到锁的悲观情绪,悲观锁认为被它保护的数据是极其不安

全的,每时每刻都有可能变动,一个事务拿到悲观锁后(可以理解为一个用户),其他任何事务

都不能对该数据进行修改,只能等待锁被释放才可以执行。

数据库中的行锁,表锁,读锁,写锁,以及 syncronized 实现的锁均为悲观锁。

这里再介绍一下什么是数据库的表锁和行锁,以免有的同学对后面悲观锁的实现看不明白。

我们经常使用的数据库是 mysql,mysql 中最常用的引擎是 Innodb,Innodb 默认使用的是行

锁。

3. 乐观锁(Optimistic Concurrency Control)

与悲观相对应,乐观是我们人类一种积极的情绪。乐观锁的“乐观情绪”体现在,它认为数据的变

动不会太频繁。因此,它允许多个事务同时对数据进行变动。 但是,乐观不代表不负责,那么

怎么去负责多个事务顺序对数据进行修改呢?乐观锁通常是通过在表中增加一个版本

(version)或时间戳(timestamp)来实现,其中,版本最为常用。

事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将

其更新到表中时,会将之前取出的版本 v1 与数据中最新的版本 v2 相对比,如果 v1 = v2 ,那么

说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修

改,并且修改时 version 会加1,以此来表明数据已被变动。如果,v1 不等于 v2,那么说明数据

变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让

其重新操作。不同于悲观锁,乐观锁是人为控制的。

4. 怎么实现悲观锁,怎么实现乐观锁

经过上面的学习,我们知道悲观锁和乐观锁是用来控制并发下数据的顺序变动问题的。

从表中可以看到猪肉脯目前的数量只有 1 个了。

在不加锁的情况下,如果 A,B 同时下单,就会报错。

4.1. 悲观锁解决

利用悲观锁的解决思路是,A 下单前先给猪肉脯这行数据(id=1)加上悲观锁(行锁)。此时这

行数据只能A 来操作,也就是只有 A 能买。B 想买就必须一直等待。当 A 买好后,B 再想去买的

时候会发现数量已经为 0,那么 B 看到后就会放弃购买。

那么如何给猪肉脯也就是 id=1 这条数据加上悲观锁锁呢?我们可以通过以下语句给 id=1 的这行

数据加上悲观锁。

select num from goods where id = 1 for update;

下面是悲观锁的加锁图解

我们通过开启 mysql 的两个会话,也就是两个命令行来演示。

1、事务 A 执行命令给 id = 1 的数据上悲观锁准备更新数据。

这里之所以要以 begin 开始,是因为 mysql 是自提交的,所以要以 begin 开启事务,否则所有修

改将被mysql 自动提交。

2、事务 B 也去给 id = 1 的数据上悲观锁准备更新数据

我们可以看到此时事务 B 再一直等待 A 释放锁。如果 A 长期不释放锁,那么最终事务B将会报

错,这有兴趣的可以去尝试一下。

3、接着我们让事务 A 执行命令去修改数据,让猪肉脯的数量减一,然后查看修改后的数据,最

后commit,结束事务。

我们可以看到,此时最后一个猪肉脯被 A 买走,只剩 0 个了。

4、当事务 A 执行完第 3 步后,我们看事务 B 中出现了什么

我们看到由于事务 A 释放了锁,事务 B 就结束了等待,拿到了锁,但是数据此时变成了 0,

那么 B 看到后就知道被买走了,就会放弃购买。

通过悲观锁,我们解决了猪肉脯购买的问题。

4.2. 乐观锁解决

下面,我们利用乐观锁来解决该问题。

上面乐观锁的介绍中,我们提到了,乐观锁是通过版本号 version 来实现的。

所以,我们需要给 goods 表加上 version 字段,表变动后的结构如下:

id、name、num、version

1 猪肉脯 1 0

2 牛肉干 1 0

具体的解决思路是,A 和 B 同时将猪肉脯(id = 1 下面都说是 id = 1)的数据查出来,然后 A 先

买,A 将id = 1 和 version = 0 作为条件进行数据更新,即将数量减一,并且将版本号加一。此时

版本号变为1。A 此时就完成了商品的购买。最后 B 开始买,B 也将 id = 1 和 version = 0 作为条

件进行数据更新,但是更新完后,发现更新的数据行数为 0,此时就说明已经有人改动过数据,

此时就应该提示用户重新查看最新数据购买。

下面是乐观锁的加锁图解

我们还是通过开启mysql的两个会话,也就是两个命令行来演示。

1、事务 A 执行查询命令,事务 B 执行查询命令,因为两者查询的结果相同,

所以下面我只列出一个截图。

此时 A 和 B 均获取到相同的数据。

2、事务 A 进行购买更新数据,然后再查询更新后的数据。![image-20230908140737474]

我们可以看到事务 A 成功更新了数据和版本号。

事务 B 再进行购买更新数据,然后我们看影响行数和更新后的数据。

可以看到最终修改行数为 0,数据没有改变。此时就需要我们告知用户重新处理。

5. 乐观锁和悲观锁的优缺点

下面我们介绍下乐观锁和悲观锁的优缺点以便我们分析他们的应用场景,

这里我只分析最重要的优缺点,也是我们要记住的。

悲观锁

  • 优点:悲观锁利用数据库中的锁机制来实现数据变化的顺序执行,这是最有效的办法。
  • 缺点:一个事务用悲观锁对数据加锁之后,其他事务将不能对加锁的数据进行除了查询以外的所有操作,如果该事务执行时间很长,那么其他事务将一直等待,那势必影响我们系统的吞吐量。

乐观锁

  • 优点:乐观锁不在数据库上加锁,任何事务都可以对数据进行操作,在更新时才进行校验,这样就避免了悲观锁造成的吞吐量下降的劣势。
  • 缺点:乐观锁因为时通过我们人为实现的,它仅仅适用于我们自己业务中,如果有外来事务插入,那么就可能发生错误。

2.6 乐观锁和悲观锁的应用场景

悲观锁

对于每一次数据修改都要上锁,如果在DB读取需要比较大的情况下有线程在执行数据修改操作

会导致读操作全部被挂载起来,等修改线程释放了锁才能读到数据,体验极差。所以比较适合用

在DB写大于读的情况。

乐观锁

乐观锁是为弥补悲观锁缺点而诞生。

由于乐观锁的不上锁特性,所以在性能方面要比悲观锁好,比较适合用在DB的读大于写的业务

场景。

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

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

相关文章

【功能自动化】自动识别测试用例

1.创建unitWebtours.py 将unitWebtours.py放在test文件夹下 unitWebtours.py 代码实现 from selenium import webdriver from selenium.webdriver.support.select import Select from time import sleep import unittestdriver Noneclass Webtours(unittest.TestCase):clas…

Ubuntu24.04 安装向日葵远程访问工具

目录 安装向日葵远程访问工具 解决方案&#xff1a; 1.下载软件包 2.远程Ubuntu桌面控制卡住 卸载向日葵远程访问工具 安装向日葵远程访问工具 安装命令&#xff1a;sudo dpkg -i 文件名.deb sudo dpkg -i SunloginClient_15.2.0.63064_amd64.deb 提示错误如下&#xf…

三级_网络技术_54_应用题

一、 请根据下图所示网络结构回答下列问题。 1.填写路由器RG的路由表项。 目的网络/掩码长度输出端口__________S0&#xff08;直接连接&#xff09;__________S1&#xff08;直接连接&#xff09;__________S0__________S1__________S0__________S1 2.如果将10.10.67.128/2…

在 CentOS 7 上安装 LNMP 环境:MySQL 8.0、PHP 8.3 和 ThinkPHP 8.0

在 CentOS 7 上安装 LNMP 环境&#xff0c;并配置 MySQL 8.0、PHP 8.3 以及 ThinkPHP 8.0&#xff0c;能够为你的 web 应用程序提供一个强大的开发和运行环境。下面是详细的安装步骤&#xff1a; --- ## 在 CentOS 7 上安装 LNMP 环境&#xff1a;MySQL 8.0、PHP 8.3 和 Thin…

基于Leaflet Legend的图例数据筛选实践-以某市教培时空分布为例

目录 前言 一、关于Leaflet.Legend组件 1、Legend组件的主要参数 2、相关参数 二、Legend图例可视化控制 1、违规教培信息的管理 2、违规培训信息时空可视化及图例渲染控制 3、成果展示 三、总结 前言 在很多的地理时空分析系统中&#xff0c;我们经常会遇到一些需求。…

第一次使用PyCharm写C++(失败)

前言&#xff1a; 由于我已经非常习惯使用PyCharm远程连接服务器了&#xff0c;我认为非常方便&#xff0c;所以希望C也能直接用Pycharm。于是尝试在PyCharm上部署C环境。 但是&#xff0c;我失败了。如果您知道问题所在&#xff0c;欢迎给我留言。我认为Pycharm并没有编译C/C…

Fedora koji构建系统详细教程之二 -- 构建

写在前面 本篇文章是上一篇文章的继续&#xff0c;由于koji里面的内容实在是太多&#xff0c;都塞进一篇文章里会显得很臃肿&#xff0c;于是我就拆成了两部分。在上一篇文章里&#xff0c;我们已经部署好了Fedora koji系统&#xff0c;此时kojihub已经运行、可以通过kojiweb或…

IOS开发 铃声制作(库乐队)

IOS开发&#xff0c;实现铃声制作功能。 在IOS端&#xff0c;要设置铃声都是通过库乐队来制作的。 先看一下库乐队中铃声的文件结构。下面是弄的一个示例的文件&#xff0c;文件信息如下&#xff1a; 我们右击文件&#xff0c;点击显示包内容如下&#xff1a; 能看到一个aiff格…

Clickhouse集群化(一)k8s集群搭建

环境准备&#xff1a; vm 17 pro 有些功能必须pro版本才会提供&#xff08;https://download.csdn.net/download/weixin_40663313/89677277?spm1001.2014.3001.5501&#xff09;夸克下载链接 centos 7.9 docker&#xff1a;1.26 k8s&#xff1a;1.21.14 1. 创建虚拟机 …

2022 年高教社杯全国大学生数学建模竞赛-C 题 古代玻璃制品的成分分析与鉴别详解+分类模型Python代码源码

前言 简单介绍一下我自己&#xff1a;博主专注建模四年&#xff0c;参与过大大小小数十来次数学建模&#xff0c;理解各类模型原理以及每种模型的建模流程和各类题目分析方法。参与过十余次数学建模大赛&#xff0c;三次美赛获得过二次M奖一次H奖&#xff0c;国赛二等奖。**提…

游戏引擎详解——图片

图片 图片的格式 图片文件格式pngjpg 纹理压缩格式ETC1/2PVRTCASTC 图片的属性 图片属性解释分辨率宽高像素值&#xff08;pt&#xff09;&#xff0c;如&#xff1a;1024*1024位深度用来存储像素颜色的值&#xff0c;如RGBA8888&#xff0c;红黄蓝透明度4个维度每个8bit&…

深度解析HarmonyOS SDK实况窗服务源码,Get不同场景下的多种模板

HarmonyOS SDK实况窗服务&#xff08;Live View Kit&#xff09;作为一个实时呈现应用服务信息变化的小窗口&#xff0c;遍布于设备的各个使用界面&#xff0c;它的魅力在于将复杂的应用场景信息简洁提炼并实时刷新&#xff0c;在不影响当前其他应用操作的情况下&#xff0c;时…

脑波传感与认知计算的深入探讨

随着神经科学和计算机技术的不断发展&#xff0c;脑波传感与认知计算逐渐成为研究的前沿领域。脑波传感技术可以捕捉人类大脑的电活动&#xff0c;从而解读大脑的思维过程和认知状态&#xff1b;而认知计算则通过模拟人类的思维方式来进行信息处理和决策。这两个领域的结合&…

红蓝对抗,你懂得多少?

红蓝对抗&#xff0c;你懂得多少&#xff1f; “HW行动”是国家应对网络安全问题所做的重要布局之一。“HW行动”从2016年开始&#xff0c;随着我国对网络安全的重视&#xff0c;涉及单位不断扩大&#xff0c;越来越多的单位都加入到HW行动中。2024年HW行动就在眼前&#xff0…

【RPC基础知识】

定义 RPC&#xff08;Remote Procedure Call&#xff09; 即远程过程调用&#xff0c;通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。 为什么要 RPC &#xff1f; 因为&#xff0c;两个不同的服务器上的服务提供的方法不在一个内存空间&#xff0c;所以&#xff…

MVC与设计模式理解-lnmp学习之路

一、MVC 前言&#xff1a; MVC是一种应用架构模式&#xff0c;也可以说是一种业务架构或是一种应用设计思想&#xff0c;用于组织业务逻辑并分离代码的。 MVC组成结构是Model-View-Controller&#xff0c;Model是管控数据层&#xff0c;View是管控视图层&#xff0c;Controlle…

基于矢量光场的光学加工技术

欢迎关注GZH《光场视觉》 摘要&#xff1a;作为光的一个基本属性&#xff0c;偏振、振幅和相位提供的自由度对光场调控具有重要作用。具有空间结构偏振态、振幅和相位分布的矢量光场因其具有不同于传统光场的独特性质而被应用于诸多领域。近年来&#xff0c;时空分布特性更加丰…

合宙LuatOS开发板使用手册——Air700EAQ

EVB-Air700EL&700EY 开发板是合宙通信推出的基于 Air700EL&700EY 模组所开发的&#xff0c; 包含电源&#xff0c;SIM 卡&#xff0c;USB&#xff0c;PCB 天线等必要功能的最小硬件系统。 以方便用户在设计前期对 模块进 行性能评估&#xff0c;功能调试&#xff0c;软…

“面试宝典:高频算法题目详解与总结”

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

JavaScript 的进阶概念补充:V8 引擎的垃圾回收机制

JavaScript 的进阶概念补充&#xff1a;V8 引擎的垃圾回收机制 JavaScript 的垃圾回收机制在各大浏览器中实现有所不同&#xff0c;其中 V8 引擎&#xff08;Google Chrome 和 Node.js 中使用的 JavaScript 引擎&#xff09;尤为知名。理解 V8 引擎的垃圾回收机制&#xff0c;…