当前位置: 首页 > article >正文

java原子操作类

在并发编程中,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,并返回新值。

示例代码:

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

示例代码:

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 问题。

示例代码:

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,并返回旧值。

示例代码:

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.kler.cn/a/312527.html

相关文章:

  • Cesium加载大量点数据卡顿处理办法
  • Docker入门系列——Docker-Compose
  • 【考研数学:高数2】数列极限
  • 【ubuntu】单进程申请4GB内存
  • 大语言模型在序列推荐中的应用
  • Vue 3 中,computed 和 watch的区别
  • 基于LSTM的文本摘要生成实战教程
  • Python学习的主要知识框架
  • 同样实用的CSS剪裁属性clip-path
  • esp32-C2 对接火山引擎实现语音转文本(二)
  • Windows安装启动:stable-diffusion-webui,AIGC大模型文生图、文生视频,Python
  • 使用mlp算法对Digits数据集进行分类
  • 必应bing广告优势,国内开户注意事项备忘录
  • Windows系统 Bat命令生成快捷方式
  • LLM - 理解 多模态大语言模型(MLLM) 的 指令微调(Instruction-Tuning) 与相关技术 (四)
  • 【例题】lanqiao3225 宝藏排序Ⅰ
  • 2-100 基于matlab的水果识别
  • 【诉讼流程-健身房-违约-私教课-诉讼书提交流程-民事诉讼-自我学习-铺平通往法律的阶梯-讲解(3)】
  • spring MVC 拦截器
  • 本地git仓库配置远程仓库的地址
  • el-table的树形结构结合多选框使用,实现单选父子联动,全选,反选功能
  • SpringBoot结合Mybatis-plus项目直接执行sql语句
  • Vue学习记录之五(组件/生命周期)
  • IO流体系(FiletOutputStream)
  • Go Testify学习与使用
  • Linux环境变量进程地址空间