【多线程】锁策略

目录

1.乐观锁 悲观锁

2.重量级锁 轻量级锁

3.自旋锁 挂起等待锁

4.读写锁

5.可重入锁  不可重入锁

6.公平锁 非公平锁

7.CAS(compare and swap)

8.基于CAS实现线程安全的方式

9.CAS应用场景

9.1 基于CAS实现原子类

9.2 基于CAS实现自旋锁 

 10.ABA问题及解决方案

11.synchronized几个重要的机制

(1)锁升级

(2)锁消除

(3)锁粗化


1.乐观锁 悲观锁

是一类锁的一种特性,不是具体的一把锁。悲观还是乐观,是对后续锁冲突是否激烈(频繁)给出的预测

如果预测接下来锁冲突的概率不大,就可以少做一些工作。称为乐观锁。

如果预测接下来锁冲突的概率很大,就应该多做一些工作,称为悲观锁。

2.重量级锁 轻量级锁

重量级锁,即锁的开销大,轻量级锁,即锁的开销小。和悲观乐观锁是有关联的。乐观锁通常也是轻量级的锁,悲观锁通常也是重量级的锁。

区别:乐观悲观锁是预测锁冲突的概率,而重量级轻量级锁则是实际消耗的开销

3.自旋锁 挂起等待锁

自旋锁就属于是轻量级锁的一种典型实现。往往是在纯用户态实现。比如使用一个while循环,不停的检查当前锁是否被释放,如果没释放,就继续循环,释放了就获取到锁并结束循环。这就相当于一种忙等,消耗CPU但是换来更快的响应速度。

挂起等待锁就属于是重量级锁的一种典型实现。需要借助系统API来实现,一旦出现所竞争了,就会在内核中触发一系列的操作。(比如让这个线程进入阻塞的状态,暂时不参与CPU调度)。

总结:

  • 上述锁策略中,乐观锁,轻量级锁,自旋锁是一组锁,可以说存在一定联系。
  • 悲观锁,重量级锁,挂起等待是一组锁,存在一定联系。

        

4.读写锁

  • 读加锁:读的时候,能读,不能加锁。
  • 写加锁:写的时候,不能读,也不能加锁。
  1. 读锁是共享锁,写锁是排它锁,读锁和写锁不能同时存在。读写锁,是把加锁操作,分成读锁和写锁。两个线程加锁过程中,读锁和读锁之间不会产生竞争(多线程读取同一个数据,没有线程安全问题),而读锁和写锁,写锁和写锁之间是有竞争的。
  2. 读锁不能升级为写锁。
  3. 写锁可以降级为读锁。

注意与数据库读写加锁的区别:

  • 数据库读加锁:  读的时候不能写。
  • 数据库写加锁:写的时候不能读。

5.可重入锁  不可重入锁

一个线程针对同一把锁,连续加锁两次,若不会死锁,就是可重入锁。若死锁,就是不可重入锁。

synchronized就是可重入锁。

6.公平锁 非公平锁

当有很多线程尝试去获取加同一把锁的时候,只有一个线程能够拿到锁,其他线程阻塞等待,这是锁的特性。那么当这个线程释放锁之后,接下来哪个线程能拿到锁?

公平锁:按照先来后到顺序获取加锁。

非公平锁:在剩下的线程当中已均等的概率来重新竞争锁。

上述这些锁策略都是描述了一把锁的基本特点的。synchronized这把锁,属于哪种锁策略呢?

  • 对于"悲观乐观",是自适应的。
  • 对于"重量轻量",是自适应的。
  • 对于"自旋挂起",是自适应的。
  • 不是读写锁。
  • 是可重入锁。
  • 是非公平锁。

初始情况下,synchronized会预测当前的锁冲突的概率,若概率不大,会以乐观锁的模式运行(也就是轻量级锁,基于自旋锁的方式实现)。若概率大,锁冲突情况多,synchronized就会升级成悲观锁,(也就是重量级锁,基于挂起等待的方式实现)。

7.CAS(compare and swap)

比如有一个内存M,还有两个寄存器A,B,CAS(M,A,B)

如果M和A的值相同的话,就把M和B里的值进行交换,返回true。如果值不相同,就什么都不做,返回false。

CAS其实是一个cpu指令,比较较换的是内存和寄存器。一个cpu指令,就能完成上述比较交换的逻辑。单个的cpu指令是原子的!!就可以使用CAS完成一些操作,进一步代替"加锁"。这给编写线程安全的代码,进入了新的思路。

8.基于CAS实现线程安全的方式

也称为"无锁编程"。

  • 优点:保证线程安全,同时避免阻塞,提高了效率。
  • 缺点:代码更加复杂,难以理解。只能够适合一些特定场景,不如加锁方式更普适。

CAS本质上是cpu提供的指令,又进一步被操作系统封装,提供成API,又因为操作系统多样,其提供的API也是不同,于是JVM对这些API又进行了统一封装,使得使用者不论在哪个操作系统上使用,只需要掌握一套API即可。下面举出几种应用场景。

9.CAS应用场景

9.1 基于CAS实现原子类

比如count++,就不是原子的,因为一个count++过程对应着cpu上三个指令(load,add,save),但是使用CAS操作会让这个过程成为原子的。

如下代码中多个线程对同一个变量进行修改就会发生线程安全问题,结果与预期不符。

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

编写代码中,我们不能直接使用CAS操作,针对这些,Java提供了API,AtomicInteger类 。atomic翻译为原子。

接下来用Java提供的 AtomicInteger类 实现。注意代码中AtomicInteger的用法。

public class Demo14 {public static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i=0;i<5000;i++) {// count++count.getAndIncrement();// ++count//count.incrementAndGet();// count--//count.getAndDecrement();// --count//count.decrementAndGet();}});Thread t2 = new Thread(() -> {for(int i=0;i<5000;i++) {count.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}

通过进一步查看 getAndIncrement 方法的源码:

 我们会发现其底层使用了CAS操作的。所谓线程不完全,实质上是进行自增的过程中,线程穿插执行了。CAS是让这里的自增不能穿插执行,核心思路是和加锁类似的。但并没有真正加锁。加锁方式是通过阻塞的方式,避免穿插。CAS则是通过重试的方式,避免穿插。

9.2 基于CAS实现自旋锁 

伪代码:

 10.ABA问题及解决方案

CAS进行操作的关键,是通过值"没有发生变化"来作为"没有其他线程穿插执行"的判断依据。

但是有一种极端的情况,可能有一个线程穿插进来,把值从 A -> B -> A了。看起来这个值是没变,但是实际上已经被穿插执行了。

举个常见的例子:去银行取钱,假设卡里有1000,我准备取走500,当我刚好取钱成功的时候,朋友给我又转了500,这样,原本我取完钱朋友给我又转了500,卡里应该有1000,但因为ABA问题可能会导致500被连续扣了两次,导致卡里只剩500。

解决办法:

  • 设置一个向一个方向增长的变量记录(不要反复横跳),即可以进入版本号,约定版本号只能增加不能减少。这样在使用CAS判断的时候,就直接判断版本号是否变化。

11.synchronized几个重要的机制

(1)锁升级

锁升级的过程是单向的,不能降级。

偏向锁其实就是没有锁竞争的时候,就吊着不加锁,这样既保证了线程安全,也保证了性能和效率,但当有锁竞争时,就会升级加锁。成为自旋锁,也就是轻量级锁。如果此时这个锁竞争的还比较激烈,就会再次升级成为重量级锁。

通俗点就是没有锁竞争时,能不加尽量不加。当有冲突时,就抢先一步加锁。因为加锁是有一定开销的,这样做既保证了线程安全,也保证了性能和效率。

锁升级的过程,就是在性能和线程安全之间尽量权衡。(因为加锁之后,虽然线程是安全了,但同时效率也变慢了)。

(2)锁消除

一种编译器优化的手段。

即编译器会自动针对你当前写的加锁的代码做出判定,如果编译器觉得这个场景,不需要加锁,此时就会把你写的synchronized给优化掉。比如常见的两种可变数组StringBuffer和StringBuilder,

  • StringBurffer 带锁 synchronized 线程安全的
  • StringBuilder 不带锁

即如果在单个线程中使用StringBuffer,此时编译器就会自动的把synchronized给优化掉。因为只要加锁就会影响性能,效率。

(3)锁粗化

锁的粒度。

synchronized里头的代码越多,就认为锁的粒度越粗,代码越少,锁的粒度就越细。

粒度细的时候,能够并发执行的逻辑就更多,更加有利于利用多核CPU资源。

但是,如果粒度粗的锁,被反复加锁解锁,可能实际效果还不如力度粗的锁(涉及到锁的反复竞争)。

例如:

 

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

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

相关文章

记录:R语言生成热图(非相关性)

今天解决了一个困扰了我很久的问题&#xff0c;就是如何绘制不添加相关性的热图。一般绘制热图是使用corrplot包画相关性图&#xff0c;但是这样有一个前提&#xff0c;就是输入的数据集必须进行相关性分析。那么如果我不需要进行相关性分析&#xff0c;而是直接绘制能够反应数…

Vue-2.8插槽

插槽分为默认插槽&#xff08;组件内定制一处结构&#xff09;、具名插槽&#xff08;组件内定制多处结构&#xff09; 作用域插槽不属于以上&#xff0c;只是插槽的一个传参语法 默认插槽 作用&#xff1a;让组件内部的一些结构支持自定义 需求&#xff1a;要在页面中显示…

项目管理的优秀软件推荐,助力提升团队效能!

我们知道&#xff0c;每个产品在上市的过程中都需要经历市场调研、研发设计、功能测试、上市评估、营销推广等阶段。作为项目经理&#xff0c;最关键的任务不仅是确保产品的顺利孵化和上市&#xff0c;还有管理团队。我们研究了许多项目管理用户&#xff0c;工作的难点是如何在…

电脑提示kernel32.dll的错误提示窗口怎么办,解决kernel32.dll丢的办法

当你在使用电脑时&#xff0c;突然收到kernel32.dll丢失或找不到的错误提示窗口&#xff0c;这个时候先不要让自己的心态爆炸&#xff0c;解决的办法会有很多种&#xff0c;其实问题都不大&#xff0c;就能够很好的解决文件缺失的问题。接下来就把方法推进给大家。 一.解决kern…

JUC并发编程:Monitor和对象结构

JUC并发编程&#xff1a;Monitor和对象结构 1. Monitor1.1 对象的结构1.1.1 MarkWord1.1.2 Klass Word1.1.3 数组长度1.1.4 &#x1f330; 1. Monitor Monitor官方文档 我们可以把Monitor理解为一个同步工具&#xff0c;也可以认为是一种同步机制。它通常被描述为一个对象&…

如何保持前端开发者的竞争力

这两年&#xff0c;我们经常听到一种声音&#xff0c;那就是“前端已死”。然而&#xff0c;事实并非如此。前端开发者在当今的软件开发中依然扮演着至关重要的角色&#xff0c;它是构建 Web 应用程序所必需的一部分&#xff0c;能够实现动态交互、良好的用户体验和友好的界面设…

文字与视频结合效果

效果展示 CSS 知识点 mix-blend-mode 属性的运用 实现整体页面布局 <section class"sec"><video autoplay muted loop><source src"./video.mp4" type"video/mp4" /></video><h2>Run</h2><!-- 用于切…

CCF CSP认证 历年题目自练Day29

题目一 试题编号&#xff1a; 202112-1 试题名称&#xff1a; 序列查询 时间限制&#xff1a; 300ms 内存限制&#xff1a; 512.0MB 样例1输入 3 10 2 5 8 样例1输出 15 样例2输入 9 10 1 2 3 4 5 6 7 8 9 样例2输出 45 题目分析&#xff08;个人理解&#xff09; 还是…

【安全】 Java 过滤器 解决存储型xss攻击问题

文章目录 XSS简介什么是XSS?分类反射型存储型 XSS(cross site script)跨站脚本攻击攻击场景解决方案 XSS简介 跨站脚本( cross site script )为了避免与样式css(Cascading Style Sheets层叠样式表)混淆&#xff0c;所以简称为XSS。 XSS是一种经常出现在web应用中的计算机安全…

TCP/IP(四)TCP的连接管理(一)三次握手

一 tcp连接回顾 部分内容来自小林coding TCP篇 记录的目的&#xff1a; 亲身参与进来,加深记忆 ① 引入 前面我们知道&#xff1a; TCP 是面向连接 [点对点的单播]的、可靠的、基于字节流的传输层通信协议面向连接意味着&#xff1a;在使用TCP之前,通信双方必须先建立一…

Linux磁盘常见知识

目录 一、基础概念 1.1 文件系统类型 1.2 主分区、扩展分区、逻辑分区三者关系 1.3 UUID 1.4 lvm逻辑卷管理系统 二. 常用命令 2.1 查看命令 2.2 分区命令 2.3 格式化命令 1.4 挂载命令 三、扩容根目录 一、基础概念 1.1 文件系统类型 文件系统类型决定了向分区中存放、读取数…

【数字人】3、LIA | 使用隐式空间来实现视频驱动单张图数字人生成(ICLR 2022)

文章目录 一、背景二、方法2.1 latent motion representation2.2 latent code driven image animation2.3 学习方式2.4 推理 三、效果3.1 数据集3.2 训练细节3.3 评估3.4 定性效果3.5 定量效果3.6 消融实验3.7 失败示例 论文&#xff1a;Latent Image Animator: Learning to An…