【Java EE】多线程-进阶-锁策略

目录

1.常见的锁策略

1.1乐观锁 vs 悲观锁

1.2重量级锁 vs 轻量级锁

1.3自旋锁(Spin Lock)

1.4公平锁 vs 非公平锁

1.5可重入锁和不可重入锁

1.6读写锁

2.CAS

2.1什么事CAS

2.2CAS是怎么实现的

2.3CAS有哪些应用

2.3.1实现原子类

2.3.2实现自旋锁

2.4CAS的ABA问题

2.4.1什么是ABA问题


1.常见的锁策略

1.1乐观锁 vs 悲观锁

悲观锁:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

乐观锁:

假设数据一般情况下并不会并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

举个例子:同学A和同学B想请教老师一个问题。

同学A认为:“老师是比较忙的,我来问问题,老师不一定有空回答”。因此同学A会先给老师发信息:“老师你忙嘛?我下午两点能来找你问个问题嘛?”(相当于加锁操作)得到肯定的答复之后,才会真的来问问题。如果得到了否定的答复,那就等一段时间,下次再来和老师确定时间。这是个悲观锁。

同学B认为:“老师是比较闲的,我来问问题,老师大概率是有空解答的”。因此同学B直接就来找老师。(没加锁,直接访问资源)如果老师确定比较闲,那么直接问题就要解决了。如果老师这会确定很忙,那么同学B也不会打扰老师,就下次再来(虽然没加锁,但是能识别出数据访问冲突)。这个是乐观锁。

Synchronized初识使用乐观锁策略。当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略

1.2重量级锁 vs 轻量级锁

锁的核心特性“原子性”,这样的机制追根溯源是CPU这样的硬件设备提供的。

  • CPU提供了“原子操作指令”。
  • 操作系统基于CPU的原子指令,实现了mutex互斥锁。
  • JVM基于操作系统提供的互斥锁,实现了synchronized 和ReetrantLock 等关键字和类。

注意:synchronized 并不仅仅是对mutex进行封装,在synchronized 内部还做了很多其他的工作

重量级锁:加锁机制重度依赖了OS提供了mutex

  • 大量的内核态用户态切换
  • 很容易引发线程的调度

这两个操作,成本比较高,一旦涉及到用户态和内核态的切换,就意味着“沧海桑田”。

轻量级锁:加锁机制尽可能不适用mutex,而是尽量在用户态代码完成,是在搞不定了,再使用mutex。

  • 少量的内核态用户态切换。
  • 不太容易引发线程调度。

理解用户态 vs 内核态

想象去银行办业务。

在窗口外,自己做,这是用户态,用户态的时间成本是比较可控的。

在窗口内,工作人员做,这是内核态,内核态的时间成本是不太可控的。、

如果办业务的时候反复和工作人员沟通,还需要重新排队,这时效率是很低的。

synchronized 开始是一个轻量级锁,如果锁冲突比较严重,就会变成重量级锁。

1.3自旋锁(Spin Lock)

按之前的方式,线程在抢锁失败后进入阻塞状态,放弃CPU,需要过很久才能再次被调度。

但实际上,大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放。没必要就放弃CPU。这个时候可以使用自旋锁来处理这样的问题。

自旋锁伪代码:

while (抢锁(lock) == 失败) {}

如果获取锁失败,立即在尝试获取锁,无限循环,直到获取到锁为止,第一次获取锁失败,第二次的尝试会在极短的时间内到来。

一旦锁被其他线程释放,就能第一时间获取到锁。

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

  • 优点:没有放弃CPU,不涉及线程阻塞和调度,一旦被释放,就能第一时间获取到锁。
  • 缺点:如果锁被其他线程持有的时间比较久,那么就会持续的消耗CPU资源(而挂起等待的时候是不消耗CPU的)。

synchronized中的轻量级锁策略大概率是通过自旋锁的方式实现的。

1.4公平锁 vs 非公平锁

假设三个线程ABC,A先尝试获取锁,获取成功。然后B再尝试获取锁,获取失败,阻塞等待;然后C也尝试获取锁,C也会获取失败,也阻塞等待。

当线程A释放锁的时候,会发生什么呢?

公平锁:遵守“先来后到”,B比C先来的,当A释放锁之后,B就能先于C获取到锁。

非公平锁:不遵守“先来后到”,B和C都有可能获取到锁。

注意:

  • 操作系统内部的线程调度就可以视为随机的,如果不做任何额外的限制,锁就是非公平锁,如果要实现公平锁,就需要依赖额外的数据结构,来记录他们的先后顺序。
  • 公平锁和非公平锁没有好坏之分,关键还是看使用场景。

synchronized是非公平锁

1.5可重入锁和不可重入锁

可重入锁的字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。

比如一个递归函数里面有加锁操作,递归过程中这个锁会阻塞自己吗?如果不会,那么这个锁就是可重入锁(因为这个原因可重入锁也叫做递归所)。

Java里只要以Reentrant开头命名的锁都是可重入的,而且JDK提供的所有现成的Lock实现类,包括synchronized关键字锁都是可重入的。

而Linux系统提供的mutex是不可重入锁。

// 第⼀次加锁, 加锁成功
lock();
// 第⼆次加锁, 锁已经被占⽤, 阻塞等待. 
lock();

synchronized是可重入锁。

1.6读写锁

多线程之间,数据的读取方之间不会产生线程安全问题,但数据的写入方互相之间以及和读者之间都需要进行互斥。如果两种场景下都用一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

读写锁,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求任何人互斥。

一个线程对于数据的访问,主要存在两种操作:读数据和写数据。

  • 两个线程都只是读一个数据,此时并没有线程安全问题,直接并发的读取即可。
  • 两个线程都要写一个数据,有线程安全问题。
  • 一个线程读另外一个线程写,也有线程安全问题。

读写锁就是把读操作和写操作区分对待,Java标准库提供了ReentrantReadWriteLock类,实现了读写锁。

  • ReetrantReadWriteLock.ReadLock类表示一个读锁。这个对象提供了lock/unlock方法进行加锁解锁。
  • ReentrantReadWriteLock.WriteLock类表示一个写锁。这个对象也提供了lock/unlock方法进行加锁解锁。

其中,

  • 读加锁和读加锁之间,不互斥。
  • 写加锁和写加锁之间,互斥。
  • 读加锁和写加锁之间,互斥。

注意,只要是涉及到“互斥”,就会产生线程的挂起等待。一旦线程挂起,再次被唤醒就不知道隔了多久了。

因此尽可能减少“互斥”的机会,就是提高效率的重要途径。

读写锁特别适合于“频繁读,不频繁写”的场景中。

Synchronized不是读写锁。

2.CAS

2.1什么事CAS

CAS:全称Compare and swap,字面意思:“比较并交换”,一个CAS涉及到以下操作:

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

  1. 比较A与V是否相等。(比较)
  2. 如果比较相等,将B写入V。(交换)
  3. 返回操作是否成功。

CAS伪代码

boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

两种典型的不是“原子性”的代码

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。

CAS可以视为是一种乐观锁。

2.2CAS是怎么实现的

针对不同的操作系统,JVM用到了不同的CAS实现原理,简单原理:

  • java的CAS利用的是unsafe这个类提供的CAS操作。
  • unsafe的CAS依赖了的事jvm针对不同的操作系统实现的Atomic::cmpxchg;
  • Atomic::cmpxchgd的实现使用了汇编的CAS操作,并使用cpu硬件提供的Lock机制保证其原子性。

简而言之,是因为硬件给予了支持,软件层面才能做到。

2.3CAS有哪些应用
2.3.1实现原子类

标准库中提供了java.util.concurrent.atomic包,里面的类都是基于这种方式来实现的。

典型的就是AtomicInteger类。其中的getAndINcrement相当于i++操作。

AtomicInteger atomicInteger = new AtomicInteger(0);
// 相当于 i++
atomicInteger.getAndIncrement();

伪代码实现:

class AtomicInteger {private int value;public int getAndIncrement() {int oldValue = value;while ( CAS(value, oldValue, oldValue+1) != true) {oldValue = value;}return oldValue;
}
}

假设两个线程同时调用getAndIncrement

  1. 两个线程都读取value的值到oldValue中。(oldValue是一个局部变量,在栈上。每个线程有自己的栈)。
  2. 线程1先执行CAS操作,由于oldValue和value的值相同,直接进行对value赋值。

注意:

  • CAS是直接读写内存的,而不是操作寄存器。
  • CAS的读内存,比较,写内存操作是一条硬件指令,是原子的。

3.线程2执行CAS操作,第一次CAS的时候发现oldValue和Value不相等,不能进行赋值,因此需要进入循环。

4.线程2接下来第二次执行CAS,此时oldValue和value相同,于是直接执行赋值操作。

5.线程1和线程2返回各自的oldValue的值即可。

通过形如上述代码就可以实现一个原子类,不需要使用重量级锁,就可以高效的完成多线程的自增操作。

本来check and set这样的操作在代码角度不是原子的。但是在硬件层面上可以让一条指令完成这个操作,也就是变成原子的了。

2.3.2实现自旋锁

基于CAS实现更灵活的锁,获取到更多的控制权。

自旋锁伪代码

public class SpinLock {private Thread owner = null;public void lock(){// 通过 CAS 看当前锁是否被某个线程持有. // 如果这个锁已经被别的线程持有, 那么就⾃旋等待. // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner = null;}
}
2.4CAS的ABA问题
2.4.1什么是ABA问题

ABA的问题:

假设存在两个线程t1和t2,有一个共享变量num,初始值A。

接下来,线程t1想使用CAS把num值改成Z,那么就需要

  • 先读取num的值,记录到oldNum变量中。
  • 使用CAS判定当前num的值是否为A,如果为A,就修改成Z。

但是,在t1执行这两个之间,t2线程可能吧num的值从A改成了B,又从B改成了A

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

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

相关文章

自动驾驶(八十四)---------中间件对比分析

很久没有写博客了,CSDN无故非法删了我第82篇,让我很恼火,一直提不起兴趣重新写一遍第82篇。但回初心,知识需要用自己的语言输出,所以今天对比分析自动驾驶中间件: 1. 中间件介绍 在自动驾驶架构中&#xf…

SAP打印输出设置

SAP打印输入有很多方式,适合不同的应用场景。 一.打印输出总体概览图 二.前台打印 这个是比较常见的,前端打印的出现减轻了管理员的工作量,用户可以选择自己电脑上的打印机输出,不需要所有打印机都在SAP平台中进行配置&#xff0…

【Next】动态路由、加载 UI 和流式传输

动态路由 动态段作为 params 属性传递给 layout、page、route 和 generateMetadata 函数。 /app/blog/[slug]/page.tsx export default function Page({params}: {params:{slug:string}}) {return <h1>Slug Page -- {params.slug}</h1> };/app/shop/[...slug]/pa…

(vue)el-select选择框加全选/清空/反选

(vue)el-select选择框加全选/清空/反选 <el-form-item label"批次"><el-selectv-model"formInline.processBatch"multiplecollapse-tagsfilterableplaceholder"请选择"style"width: 250px"no-data-text"请先选择企业、日…

常用的启发式算法有哪些

常用的启发式算法主要包括模拟退火算法&#xff08;SA&#xff09;、蚁群算法&#xff08;ACO&#xff09;、粒子群算法&#xff08;PSO&#xff09;、遗传算法&#xff08;GA&#xff09;、禁忌搜索算法&#xff08;TS&#xff09;以及超启发式算法&#xff08;Hyper-Heuristi…

Java使用documents4j将word和excel转pdf

pom.xml添加documents4j依赖 <!-- documents4j --> <dependency><groupId>com.documents4j</groupId><artifactId>documents4j-local</artifactId><version>1.0.3</version> </dependency> <!-- documents4j 转 wor…

《Kubernetes部署篇:基于Kylin V10+ARM架构CPU+外部etcd使用containerd部署K8S 1.26.15容器版集群(一主多从)》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;企业级K8s集群运维实战 1、在当前实验环境中安装K8S1.25.14版本&#xff0c;出现了一个问题&#xff0c;就是在pod中访问百度网站&#xff0c;大…

Java基础知识总结(59)

&#xff08;1&#xff09;Set集合 Set也是集合中的一个容器&#xff0c;程序可以依次把若干个对象放进Set&#xff0c;但是Set无法保存元素添加的顺序&#xff0c; Set不允许包含相同的元素&#xff0c;如果把两个相同的元素加入同一个Set中&#xff0c;则添加失败&#xff0…

Openlayers动态刷新Arcgis MapServer服务图层

Openlayers动态刷新Arcgis MapServer服务图层 最近做一个图层编辑的功能&#xff0c;在前端新增一个图块之后需要刷新Arcgis切片服务&#xff0c;实时显示效果。刚开始使用source的refresh方法&#xff0c;结果不生效&#xff0c;后来在source上更新一个时间戳参数实现了刷新切…

UTS iOS插件

1、UTS插件无法出现 再uniapp x中使用时&#xff0c;必须给这个插件高度和宽度&#xff0c;否则出不来&#xff01; <uts-hello-view buttonText"点击按钮内容" style"width:375px;height: 375px;background-color: aqua;"></uts-hello-view>…

python后端相关知识点汇总(十二)

python知识点汇总十二 1、什么是 C/S 和 B/S 架构2、count(1)、count(*)、count(列名)有啥区别&#xff1f;3、如何使用线程池3.1、为什么使用线程池&#xff1f; 4、MySQL 数据库备份命令5、supervisor和Gunicorn6、python项目部署6.1、entrypoint.sh制作6.2、Dockerfile制作6…

vue 的报告页面,生成pdf,使用html2canvas , 下载pdf格式文件。多页分页下载

1. 新建一个js 文件 &#xff0c; 命名 为 html2canvas.js ,html2canvas 文件和jspdf.min.js 放同一目录下。下载文件已上传啦 2. 在vue 文件中引入html2canvas.js 文件 <script>import * as html2Canvas from ./html2canvas.js </script> 3 点击下载&#xff…

oracle insert操作分批量提交

对临时表做insert插入时没有做批量提交&#xff0c;可能会导致undo表空间撑爆&#xff0c;修改脚本对插数进行2万一次的批量提交&#xff0c;并且修改索引和同义词创建时间在插数操作结束后。 原语句&#xff1a; insert into 目标表 select * from 源表;改为2w次一提交&…

OpenCV基本图像处理操作(十)——图像特征harris角点

角点 角点是图像中的一个特征点&#xff0c;指的是两条边缘交叉的点&#xff0c;这样的点在图像中通常表示一个显著的几角。在计算机视觉和图像处理中&#xff0c;角点是重要的特征&#xff0c;因为它们通常是图像中信息丰富的区域&#xff0c;可以用于图像分析、对象识别、3D…

探索人工智能在医疗领域的革命性应用

人工智能&#xff08;AI&#xff09;正在全球范围内逐步改变医疗行业的面貌&#xff0c;从提高诊断精度到优化治疗方案&#xff0c;AI技术正成为医疗创新的核心驱动力。本文将深入探讨AI在医疗领域的应用&#xff0c;并通过一个具体的Python代码示例&#xff0c;展示如何利用机…

30天算法挑战赛,一天一个算法(第一天,二分查找)

二分查找 1. 板子 int left 0; int right n - 1; while (left < right) {int mid (right - left) / 2 left;if (nums[mid] < target) {left mid 1;} else {right mid - 1;}return left; }2. 应用题 1. 查找题单 在排序数组中查找元素的第一个和最后一个位置 搜…

JavaIO操作-File

1、File类 1.1 File类说明 存储在变量,数组和对象中的数据是暂时的,当程序终止时他们就会丢失.为了能够永 久的保存程序中创建的数据,需要将他们存储到硬盘或光盘的文件中.这些文件可以移动,传送,亦可以被其他程序使用.由于数据存储在文件中,所以我们需要学习一个和文件有密…

openharmony 编译源码及docker相关操作

目录 一、环境搭建: 二、编译链下载及相关配置 三、编译及版本烧录 一、环境搭建: 1、拉取docker镜像,openharmony。默认版本为ubuntu 18.04

DDoS攻击愈演愈烈,谈如何做好DDoS防御

DDoS攻击是目前最常见的网络攻击方式之一&#xff0c;各种规模的企业包括组织机构都在受其影响。对于未受保护的企业来讲&#xff0c;每次DDoS攻击的平均成本为20万美元。可见&#xff0c;我们显然需要开展更多的DDoS防御工作。除考虑如何规避已发生的攻击外&#xff0c;更重要…

【LeetCode热题100】【动态规划】零钱兑换

题目链接&#xff1a;322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; 要拿硬币凑钱&#xff0c;硬币无限多&#xff0c;就是完全背包问题&#xff0c;定义dp[i]是要凑的钱i的硬币数&#xff0c;对于当前硬币来说&#xff0c;如果选择了这个硬币&#xff0c;要么要凑的…