CAS解析和 synchronized 优化过程

devtools/2024/9/23 19:24:28/

目录

正文:

1.synchronized的优化过程

1.1锁粗化与锁细化

1.2自旋锁

1.3锁消除

1.4 偏向锁

1.5. 轻量级锁

1.6 重量级锁

CAS-toc" style="margin-left:80px;"> 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 。执行加锁操作,会先进入内核态, 在内核态判定当前锁是否已经被占用 ,如果该锁没有占用,则加锁成功,并切换回用户态。 如果该锁被占用,则加锁失败。 此时线程进入锁的等待队列,挂起,等待被操作系统唤醒.。经过一段时间后,这个锁被其他线程释放了,操作系统也想起了这个挂起的线程,于是唤醒 这个线程,尝试重新获取锁。

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

CAS" style="background-color:transparent;"> 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.ppmy.cn/devtools/13235.html

相关文章

Java进阶-日志框架

概述 小结 体系 Logback概述 Logback快速入门 1.下载 一般情况&#xff0c;Logback日志框架只需要下载slf4j-api、logback-core、logback-classic这三个jar包即可。 slf4j-api-1.7.26.jar官网下载链接&#xff1a; https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7…

什么是用户体验(UX)文案,为什么它很重要?

网上购物如今比以往任何时候都更加相关。所以我们将以此为例说明什么是用户体验&#xff08;UX&#xff09;文案&#xff0c;以及为什么它很重要。 假设你去了一个在线商店。你需要执行一系列操作&#xff1a; 找到合适的部分选择你感兴趣的产品弄清楚它们是什么&#xff0c;…

Python自学之路--002:Python 如何生成exe可执行文件

目录 1、概述 2、安装pyinstall 3、终端指令 1、概述 大部分时候&#xff0c;执行的仅仅是一个Python解释器出来的文件&#xff0c;至于怎么将文件生成exe的可执行文件呢&#xff1f;Python有对应的库&#xff0c;也就是pyinstall。安装之后产生dist文件夹&#xff0c;里面就…

网络通信安全

一、网络通信安全基础 TCP/IP协议简介 TCP/IP体系结构、以太网、Internet地址、端口 TCP/IP协议简介如下&#xff1a;&#xff08;from文心一言&#xff09; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff0c;传输控制协议/网际协议&#xff0…

计算机网络原原理学习资料分享笔记---第一章/第六节(为有梦想的自己加油!)

第六节 计算机网络与 因特网发展简史 第六节 计算机网络与因特网发展简史 知识点 1 计算机网络与因特网发展简史 第六节 计算机网络与因特网发展简史 3 、 1972 年底&#xff0c; ARPAnet已经发展到 15 个交换结点。 4 、 20 世纪 70 年代早期与中期&#xff0c;除了ARPAn…

基于springboot的教学资源库源码数据库

基于springboot的教学资源库源码数据库 社会的进步&#xff0c;教育行业发展迅速&#xff0c;人们对教育越来越重视&#xff0c;在当今网络普及的情况下&#xff0c;教学模式也开始逐渐网络化&#xff0c;各大高校开始网络教学模式。 本文研究的教学资源库系统基于Springboot…

GaussDB 数据导入导出工具介绍

本节课程一起来学习一下GaussDB数据库的导入导出。 目录 一、数据导入导出场景划分 1. gsql工具适用场景和使用方法 2. copy使用场景和使用方法 3.gs_dump工具使用方法 4. gs_restore工具使用方法 二、gs_loader介绍 1. 工具介绍 2.创建系统表和数据表 3.创建控制文…

Qt - 窗口

目录 1. 前言 2. 菜单栏(QMenuBar) 2.1. 创建菜单栏 2.1.1. 方式一 2.1.2. 方式二 2.2. 在菜单栏中添加菜单和创建菜单项 2.3. 在菜单项之间添加分割线 2.4. 综合示例 3. 工具栏(QToolBar) 3.1. 创建工具栏 3.2. 设置停靠位置 3.2.1. 方式一 3.2.2. 方式二 3.3. 设…