java原子操作类

devtools/2024/9/23 22:20:40/

在并发编程中,Java 提供了一组原子操作类来确保线程安全,这些类位于 java.util.concurrent.atomic 包中。这些类通过底层硬件支持的CAS(Compare-And-Swap,比较并交换)机制,能够在无锁的情况下实现对变量的安全更新。这种无锁机制不仅能确保线程安全,还能避免传统锁带来的上下文切换开销,极大提高了并发性能。

1. 原子操作的背景

在多线程编程中,多个线程可能同时操作共享的变量。如果不使用同步机制(如 synchronizedLock),多个线程的并发修改可能导致数据不一致的情况。为了解决这个问题,Java 提供了 Atomic 类,它们支持一种非阻塞的原子性操作。

  • 原子性:是指在一次操作过程中,中间不会被其他线程打断,保证操作的完整性。
  • CAS:原子操作类依赖 CAS 实现线程安全,CAS 是一种乐观锁机制,它比较当前变量的值与预期值,如果相等就更新,否则重试。这种机制通常会比阻塞锁更加高效。

2. 原子操作类的分类

Java 提供了多种原子操作类,主要分为以下几类:

  1. 原子更新基本类型

    • AtomicInteger:对 int 类型的原子操作。
    • AtomicLong:对 long 类型的原子操作。
    • AtomicBoolean:对 boolean 类型的原子操作。
  2. 原子更新数组类型

    • AtomicIntegerArray:对 int[] 数组的原子操作。
    • AtomicLongArray:对 long[] 数组的原子操作。
    • AtomicReferenceArray<E>:对对象数组的原子操作。
  3. 原子更新引用类型

    • AtomicReference<V>:对普通对象引用的原子操作。
    • AtomicStampedReference<V>:带有版本戳的原子引用,可以解决 ABA 问题。
    • AtomicMarkableReference<V>:带有标记位的原子引用。
  4. 原子更新对象属性类型

    • AtomicIntegerFieldUpdater<T>:对 int 类型的某个类的字段进行原子更新。
    • AtomicLongFieldUpdater<T>:对 long 类型的某个类的字段进行原子更新。
    • AtomicReferenceFieldUpdater<T,V>:对对象引用类型的某个类的字段进行原子更新。

3. 常用原子操作类详解

3.1 AtomicInteger

AtomicInteger 是最常用的原子操作类之一,它用于对 int 类型的变量进行原子更新。常见的方法包括:

  • get():获取当前值。
  • set(int newValue):设置新值。
  • compareAndSet(int expect, int update):如果当前值等于 expect,则将其更新为 update
  • getAndIncrement():以原子方式将当前值加 1,并返回旧值。
  • incrementAndGet():以原子方式将当前值加 1,并返回新值。

示例代码:

java">import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerExample {private static final AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) {// 多线程并发递增for (int i = 0; i < 10; i++) {new Thread(() -> {int newValue = counter.incrementAndGet(); // 原子递增System.out.println(Thread.currentThread().getName() + " - Counter: " + newValue);}).start();}}
}

在上面的代码中,多个线程可以并发递增 counter,且不需要使用锁来保证线程安全,因为 AtomicInteger 使用 CAS 实现了无锁的原子性递增。

3.2 AtomicReference

AtomicReference 用于对引用类型(对象)的原子更新。它与 AtomicInteger 类似,但操作的是对象而非基本类型。常用方法包括:

  • get():获取当前对象的引用。
  • set(V newValue):设置新引用。
  • compareAndSet(V expect, V update):如果当前引用等于 expect,则将其更新为 update

示例代码:

java">import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceExample {private static final AtomicReference<String> ref = new AtomicReference<>("Initial Value");public static void main(String[] args) {// 多线程并发更新引用for (int i = 0; i < 3; i++) {new Thread(() -> {String oldValue = ref.get();String newValue = oldValue + " Updated by " + Thread.currentThread().getName();if (ref.compareAndSet(oldValue, newValue)) {System.out.println(Thread.currentThread().getName() + " - Updated: " + newValue);} else {System.out.println(Thread.currentThread().getName() + " - Update failed");}}).start();}}
}

这个示例展示了如何使用 AtomicReference 来保证多个线程并发更新对象引用时的原子性。compareAndSet 方法确保只有预期的引用被更新,避免了并发修改问题。

3.3 AtomicStampedReference

AtomicStampedReferenceAtomicReference 的扩展,它解决了ABA 问题。ABA 问题是指在并发环境中,一个变量可能被修改为旧值,然后再次被修改回来,但这个变化可能会被忽略,因为在 CAS 比较时,值没有发生变化。AtomicStampedReference 通过为每次操作打上“戳”,来检测是否出现 ABA 问题。

示例代码:

java">import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicStampedReferenceExample {private static final AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("Initial", 0);public static void main(String[] args) {int[] stampHolder = new int[1];String oldValue = stampedRef.get(stampHolder); // 获取当前值和戳int stamp = stampHolder[0]; // 当前戳System.out.println("Old Value: " + oldValue + ", Stamp: " + stamp);// 更新值和戳boolean result = stampedRef.compareAndSet(oldValue, "Updated", stamp, stamp + 1);if (result) {System.out.println("Update success. New Value: " + stampedRef.getReference() + ", New Stamp: " + stampedRef.getStamp());} else {System.out.println("Update failed.");}}
}

在这个示例中,AtomicStampedReference 通过戳来解决了 ABA 问题。每次修改时都会比较值和戳,确保引用的值和版本戳同时满足预期。

3.4 AtomicIntegerArray

AtomicIntegerArray 是对 int 数组的原子操作类。它支持对数组中每个元素进行原子操作,而不需要对整个数组加锁。常见的方法包括:

  • get(int i):获取数组第 i 个元素的值。
  • set(int i, int newValue):设置第 i 个元素的新值。
  • getAndIncrement(int i):将数组第 i 个元素加 1,并返回旧值。

示例代码:

java">import java.util.concurrent.atomic.AtomicIntegerArray;public class AtomicIntegerArrayExample {private static final AtomicIntegerArray atomicArray = new AtomicIntegerArray(5);public static void main(String[] args) {// 启动多个线程操作数组for (int i = 0; i < 5; i++) {int index = i;new Thread(() -> {int oldValue = atomicArray.getAndIncrement(index);System.out.println(Thread.currentThread().getName() + " - Index: " + index + ", Old Value: " + oldValue + ", New Value: " + atomicArray.get(index));}).start();}}
}

AtomicIntegerArray 允许多个线程安全地并发操作同一个数组的不同元素,而不会引发竞争条件。

4. CAS 操作原理

原子操作类的核心是 CAS 操作。CAS 全称为“Compare and Swap”,即比较并交换,它依赖于硬件支持来实现无锁并发控制。CAS 包含三个操作数:

  • 内存位置(V):即变量的内存地址。
  • 期望值(A):当前线程期望变量的值。
  • 新值(B):当前线程希望将变量更新为的新值。

CAS 的工作原理是:如果内存位置 V 的值与期望值 A 相等,则将内存位置

V 的值更新为 B;否则,不做任何操作。CAS 操作通常通过 CPU 指令(如 cmpxchg)来实现,是一种高效的无锁机制。

5. 应用场景与性能

5.1 应用场景
  • 计数器:使用 AtomicInteger 作为全局计数器,确保多线程下的准确性。
  • 对象引用更新AtomicReference 可用于安全更新共享资源的引用,避免竞争条件。
  • 数组更新AtomicIntegerArray 用于高并发场景中对数组元素的安全更新。
  • ABA 问题解决:在并发环境中,使用 AtomicStampedReference 可以防止 ABA 问题。
5.2 性能

原子操作类由于采用 CAS 无锁机制,通常在高并发场景下性能优于传统的锁机制。特别是在多核处理器上,CAS 操作能避免上下文切换的开销,使得其在高频率的读写操作中表现更加优异。

6. 总结

Java 的原子操作类通过无锁的方式保证了线程安全,它们提供了对基本类型、数组、引用等的原子操作,在并发环境下既保证了数据的一致性,又避免了传统锁机制带来的性能损失。CAS 机制是原子操作类的核心,使得这些类在高并发场景中表现出色。合理使用这些类,能够在多线程编程中极大提升应用的并发性能。


http://www.ppmy.cn/devtools/116202.html

相关文章

maxwell 输出消息到 kafka

文章目录 1、kafka-producer2、运行一个Docker容器&#xff0c;该容器内运行的是Zendesk的Maxwell工具&#xff0c;一个用于实时捕获MySQL数据库变更并将其发布到Kafka或其他消息系统的应用3、进入kafka容器内部4、tingshu_album 数据库中 新增数据5、tingshu_album 数据库中 更…

Qt 模型视图(二):模型类QAbstractItemModel

文章目录 Qt 模型视图(二)&#xff1a;模型类QAbstractItemModel1.基本概念1.1.模型的基本结构1.2.模型索引1.3.行号和列号1.4.父项1.5.项的角色1.6.总结 Qt 模型视图(二)&#xff1a;模型类QAbstractItemModel ​ 模型/视图结构是一种将数据存储和界面展示分离的编程方法。模…

Apache CVE-2021-41773 漏洞攻略

1.环境搭建 docker pull blueteamsteve/cve-2021-41773:no-cgid docker run -d -p 8080:80 97308de4753d 2.使用poc curl http://192.16.10.190:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/etc/passwd 3.工具验证

203. 移除链表元素

203. 移除链表元素 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5] 示例 2&#x…

java反射学习总结

最近在项目上有一个内部的CR&#xff0c;运用到了反射。想起之前面试的时候被面试官追问有没有在项目中用过反射&#xff0c;以及反射的原理和对反射的了解。 于是借此机会&#xff0c;学习回顾一下反射&#xff0c;以及在项目中可能会用到的场景。 Java 中的反射概述 反射&…

超越YOLO检测一切!最强开集目标检测模型登场!学会这思路发文效率直接起飞

还记得去年超火的Grounding DINO吗&#xff1f;最近IDEA研究院推出了它的全新升级版——Grounding DINO 1.5。这个升级版有俩版本&#xff0c;Pro版更强&#xff0c;Edge版更快。但无论是哪个版本都刷爆了目标检测SOTA&#xff0c;超越YOLO&#xff01; 这种文本输入、即时识别…

改进拖放PDF转换为图片在转换为TXT文件的程序

前段时间我写了Python识别拖放的PDF文件再转成文本文件-CSDN博客 最近有2点更新&#xff0c;一是有一些pdf文件转换出来的图片是横的&#xff0c;这样也可以识别文字&#xff0c;但是可能会影响效果&#xff0c;另一个是发现有一些文字识别不出来&#xff0c;看了关于提高Padd…

把任务管理器里面的vmware usb arbitrition停了,虚拟机一直识别不到手机设备了

在设备管理器--服务 里面找到VMware usb arbitrition服务&#xff0c;点击“启用”就好了。 参考大佬的文章&#xff1a; 吐血经验&#xff01;&#xff01;&#xff01;解决虚拟机连不上USB&#xff01;最全&#xff01;_为什么vmware虚拟机不能连接上usb设备-CSDN博客