CAS解析和 synchronized 优化过程

目录

正文:

1.synchronized的优化过程

1.1锁粗化与锁细化

1.2自旋锁

1.3锁消除

1.4 偏向锁

1.5. 轻量级锁

1.6 重量级锁

 2.CAS

2.1概述

2.2java中的cas操作

 2.3ABA问题

总结:


正文:

1.synchronized的优化过程

synchronized 是 Java 中用于实现同步和线程安全的关键字。在 Java 早期版本中,synchronized 的实现较为简单,但它在面对高并发场景时存在一些性能问题。随着 Java 虚拟机(JVM)的发展,synchronized 经历了一系列的优化,以提高其在并发环境下的性能。

JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。会根据情况,进行依次升级。

1.1锁粗化与锁细化

为了减少频繁的锁申请和释放操作,JVM 引入了锁粗化和锁细化技术。

  • 锁粗化:将多个连续的锁操作合并为一个更大范围的锁,减少锁的申请和释放次数。
  • 锁细化:将一个大范围的锁分解为多个小范围的锁,以减少锁的争用。
1.2自旋锁

为了减少线程在等待锁时的阻塞和上下文切换开销,JVM 引入了自旋锁。当一个线程尝试获取一个已被其他线程持有的锁时,它不是立即阻塞,而是在当前线程上执行一个循环(自旋),等待锁被释放。如果锁在短时间内被释放,自旋锁可以显著提高性能。同时JVM 会根据历史锁的获取情况来调整自旋的次数,这种自旋称为适应性自旋。如果一个锁通常在自旋后能够很快被获取,那么 JVM 会增加自旋的次数;反之,如果自旋很少成功,JVM 会减少自旋次数,避免浪费处理器资源。

1.3锁消除

JVM 通过逃逸分析,识别出一些不可能被其他线程访问的共享资源,从而在编译时消除对这些资源的锁操作。锁消除减少了不必要的同步开销,提高了程序的运行效率。

1.4 偏向锁

为了减少在没有锁竞争的情况下的同步开销,JVM 引入了偏向锁。偏向锁会偏向于第一个获取它的线程,这个线程在后续获取锁时不需要进行任何同步操作。只有当另一个线程尝试获取这个偏向锁时,偏向模式才会被撤销,并根据情况可能升级为轻量级锁或重量级锁。偏向锁不是真的 "加锁", 只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程. 如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销) 如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前 申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态。

1.5. 轻量级锁

当偏向锁被打破,即有其他线程尝试获取这个锁时,JVM 会尝试使用轻量级锁。轻量级锁依赖于 CAS 操作来实现线程间的同步。如果 CAS 操作成功,线程获得锁;如果失败,线程可以自旋等待或者阻塞等待。自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源。因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了,也就是所谓的 "自适应"。

1.6 重量级锁

如果竞争进一步激烈,自旋不能快速获取到锁状态, 就会膨胀为重量级锁 此处的重量级锁就是指用到内核提供的 mutex 。执行加锁操作,会先进入内核态, 在内核态判定当前锁是否已经被占用 ,如果该锁没有占用,则加锁成功,并切换回用户态。 如果该锁被占用,则加锁失败。 此时线程进入锁的等待队列,挂起,等待被操作系统唤醒.。经过一段时间后,这个锁被其他线程释放了,操作系统也想起了这个挂起的线程,于是唤醒 这个线程,尝试重新获取锁。

简单来说,会有以下几个过程:

 2.CAS

2.1概述

CAS(Compare-And-Swap)是一种乐观锁机制,用于实现对共享变量的原子操作。CAS 通过原子性地比较内存中的值和预期的值,当两者相等时,更新内存中的值为新值。CAS 操作包括三个步骤:读取变量当前值、比较变量当前值和期望值、更新变量值。如果当前值等于期望值,则更新变量值,否则重试。CAS 操作是在硬件层面上提供的原子操作,可以保证并发访问下的数据一致性。

CAS 操作包含三个参数:内存位置(V),预期原值(A)和新值(B)。CAS 的基本步骤如下:

  1. 读取内存位置 V 的当前值。
  2. 检查当前值是否与预期原值 A 相等。
  3. 如果相等,将内存位置 V 更新为新值 B。
  4. 如果不相等,操作失败,可以选择重试或放弃。

CAS 操作是原子的,这意味着在整个比较和交换的过程中,内存位置 V 不会被其他线程修改。

CAS伪代码(下面写的代码不是原子的, 真实的 CAS 是一个原子的硬件指令完成的)

boolean CAS(address, expectValue, swapValue) {
if (*address == expectedValue) {*address = swapValue; return true;}
return false;
}
2.2java中的cas操作

Java 中的 CAS 操作是通过 java.util.concurrent.atomic 包中的原子类实现的。这些原子类利用了 CPU 指令集(如 CMPXCHG)中的原子操作来保证在多线程环境下变量的更新是“看起来”无锁的(lock-free)。Java 提供了一系列原子类,位于 java.util.concurrent.atomic 包中,如 AtomicIntegerAtomicLongAtomicReference 等。这些原子类提供了一些常见的原子操作方法,如 getAndIncrement()compareAndSet()getAndSet() 等。

下面以 AtomicInteger 类为例,介绍一些常用的原子操作方法:

  1. get():获取当前值。
  2. getAndIncrement():获取当前值并自增。
  3. compareAndSet(int expect, int update):比较当前值与期望值,如果相等则更新为新值。
  4. getAndSet(int newValue):设置新值并返回旧值。

下面是一个简单的示例代码,演示了如何使用 AtomicInteger 类进行原子操作:

import java.util.concurrent.atomic.AtomicInteger;public class casExample {//原子变量private 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();}});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);}
}

使用 AtomicInteger 类实现原子操作,在两个线程中分别对共享的 AtomicInteger 类型变量 count 进行自增操作,最终输出结果10000。

 2.3ABA问题

定义:

在多线程环境中,当线程 A 读取一个变量的值(假设为 A),在这个过程中线程 B 也读取了该值,并将变量修改为其他值(假设为 B),随后线程 B 又将变量改回原来的值 A。此时,线程 A 进行 CAS 操作,由于读取的值没有变化(仍然是 A),CAS 操作可能会成功,尽管实际上该值已经被其他线程修改过。

ABA 问题可能导致以下影响:

  • 数据不一致:由于 CAS 操作在值未变化的假象下成功,可能会导致基于该值的逻辑产生不一致的结果。
  • 逻辑错误:在某些情况下,ABA 问题可能导致程序逻辑错误,因为程序可能期望在值发生变化时执行某些操作,但由于 ABA 问题,这些操作可能被遗漏。

示例代码

import java.util.concurrent.atomic.AtomicReference;public class test {public static void main(String[] args) throws InterruptedException {AtomicReference<Integer> ref = new AtomicReference<>(1);Thread t1 = new Thread(() -> {int original = ref.get();System.out.println("原始值为 " + original); // 输出原始值// 模拟值被其他线程修改并恢复ref.set(2);ref.set(original);// 即使 ref 的值恢复为 original,CAS 操作仍然会成功boolean success = ref.compareAndSet(original, 3);System.out.println("cas操作是否成功 " + success); // 输出 CAS 操作结果});t1.start();t1.join();System.out.println("cas操作后的值:" + ref);}
}

输出结果为

 

在这个例子中,主线程首先读取了 ref 的值为 1,然后模拟了其他线程将 ref 的值修改为 2 后又改回 1 的过程。即使 ref 的值被恢复了,由于 CAS 操作是基于值比较的,它仍然会认为操作成功,在特定的情况下就会造成问题。

解决方法:

有几种策略可以解决或缓解 ABA 问题:

  • 版本号:在每个对象中加入一个版本号,每次修改对象时,版本号递增。CAS 操作不仅比较对象的值,还要比较版本号,从而确保即使值相同,版本号不同也能检测到变化。
  • AtomicStampedReference:Java 提供了 AtomicStampedReference 类,它通过引入一个“stamp”(类似于版本号)来解决 ABA 问题。每次更新对象时,stamp 也会更新,从而避免了 ABA 问题。
  • 锁机制:虽然不是解决 ABA 问题的直接方法,但在某些情况下,使用锁(如 synchronized 或 ReentrantLock)来保证操作的原子性和可见性,也是一种可行的替代方案。

示例代码

import java.util.concurrent.atomic.AtomicStampedReference;public class ABASolutionExample {public static void main(String[] args) {AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 1);int[] stamp = new int[1];stamp[0] = asr.getStamp();Integer current = asr.getReference();current = new Integer(2); // 模拟值被其他线程修改asr.compareAndSet(1, current, stamp[0], stamp[0] + 1); // 模拟恢复原始值stamp[0]++;boolean success = asr.compareAndSet(1, 3, stamp[0] - 1, stamp[0]);System.out.println("CAS operation success: " + success); // 输出 CAS 操作结果}
}

在这个例子中,即使 asr 的引用值被恢复为 1,但由于 stamp 值已经增加,CAS 操作将会失败。

总结:

锁策略在多线程编程中起着至关重要的作用,合理选择和优化锁策略可以提高程序的性能和可靠性。对于一些需要高性能的场景,想要保证线程安全如果单单靠加锁无法满足要求,可以考虑使用cas来进行优化。

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

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

相关文章

Git | Git基本命令

Git | Git基本操作 文章目录 Git | Git基本操作一、创建Git本地仓库1、创建Git仓库2、配置Git3、理解工作区、暂存区、版本库关系 二、添加、修改与查看添加文件查看历史提交记录 修改文件查看.git文件 三、版本回退版本回退撤销修改尚未add已add但还未commit已add并commit 删除…

STM32F4以太网 (ETH)之精简介质独立接口:RMII

目录 概述 1 以太网简介 1.1 介绍 1.2 特征 2 以太网功能说明&#xff1a; RMII 3 RMII接口 3.1 接口介绍 3.2 精简介质独立接口信号 3.3 RMII 时钟源 3.4 RMII 选择 3.5 RMII内部时钟方案 4 RMII工作时序 4.1 发送序列 ​4.2 发送时序图 4.3 RMII时序参数 5 …

InFusion:通过从扩散先验学习深度完成来修复3D高斯

InFusion: Inpainting 3D Gaussians via Learning Depth Completion from Diffusion Prior InFusion&#xff1a;通过从扩散先验学习深度完成来修复3D高斯 Zhiheng Liu * 刘志恒 *1144Hao Ouyang * 欧阳浩 *2233Qiuyu Wang 王秋雨33Ka Leong Cheng 郑家亮2233Jie Xiao 街小…

美盈森携手飞讯打造SRM项目驱动供应链价值跃升

日前&#xff0c;美盈森集团股份有限公司&#xff08;以下简称&#xff1a;美盈森&#xff09;携手飞讯工业互联共同启动了以“协同创新&#xff0c;驱动供应链价值跃升”为主题的SRM项目。美盈森厂长袁训光、严光友、周振华等领导携同公司各职能部门的核心成员齐聚现场&#x…

Docker构建Golang项目常见问题

Docker构建Golang项目常见问题 1 Dockerfile1.1 dockerfile报错&#xff1a;failed to read expected number of bytes: unexpected EOF1.2 go mod tidy: go.mod file indicates go 1.21, but maximum supported version is 1.171.3 是否指定启动文件问题 2 构建及部署 1 Docke…

CSS的语法规则——基础选择器

元素&#xff1a; 用法&#xff1a; 标签名&#xff1a;{style的内容} 特点&#xff1a; 全局性&#xff0c;使用后&#xff0c;所有的相同标签都是同一种样式。 举例&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UT…

Docker - Compose

原文地址&#xff0c;使用效果更佳&#xff01; Docker - Compose | CoderMast编程桅杆Docker - Compose 在部署应用时&#xff0c;常常使用到不止一个容器&#xff0c;那么在部署容器的时候就需要一个一个进行部署&#xff0c;这样的部署过程也相对来说比较繁琐复杂&#xff…

医学图像分割入门-FCN理论与实践

FCN&#xff08;全卷积神经网络&#xff09; 引言 全卷积网络&#xff08;Fully Convolutional Network&#xff0c;简称FCN&#xff09;是一种深度学习模型&#xff0c;专门设计用于图像分割任务。相比于传统的基于全连接层的神经网络&#xff0c;FCN可以接受任意尺寸的输入…

「健身党」真无线蓝牙耳机怎么选?攻略来啦

在减脂健身的道路上,我虽然还是个新手,但在耳机选择上却已经踩了不少雷。根据自己的经验分享一些挑选真无线蓝牙耳机的小技巧,希望能为大家提供一些帮助,避免像我一样花冤枉钱。 一、耳机类型 传统入耳式耳机和头戴式耳机真的达咩,健身的时候戴这两种耳机不仅容易掉,而且舒适…

Stanford天空图像和光伏发电数据集

需要的同学私信联系&#xff0c;推荐关注上面图片右下角的订阅号平台 自取下载。 太阳能的间歇性对光伏&#xff08;PV&#xff09;与电网的大规模集成提出了挑战。基于天空图像的太阳能预报已被认为是预测短期波动的一种很有前途的方法。在这里&#xff0c;介绍一个数据集名叫…

详解QListView、QListWidget、QTableView、QTableWidget的使用以及区别

在Qt框架中&#xff0c;QListView、QListWidget、QTableView和QTableWidget都是用于显示列表或表格数据的控件。它们在用途、数据模型、灵活性以及直接操作数据的便捷性等方面存在一定的差异。下面将详细阐述这些控件的使用方法以及它们之间的区别&#xff0c;并提供相应的C代码…

算法课程笔记——蓝桥云课第二次直播

注意是‘’ 都正确 可以理解为a的首地址也是数字&#xff0c;向右1 %p逻辑地址 Cin cout字符串“”单个字符本身‘’&#xff0c;其他时候不用加 这样就可以 逆运算 bool比较真假<从小到大排 11/25 都输出最省事 变成长度为n1的数组 考虑到整个都可能为一个颜色&#xff0c;…