【Java】Atomic原子类总结


Java Atomic原子类总结

前言

在Java并发编程中,保证线程安全是一个核心问题。传统的锁机制(如synchronized和ReentrantLock)虽然能够解决线程安全问题,但存在性能开销大、可能导致线程阻塞等缺点。为了提供更高效的线程安全解决方案,Java并发包(java.util.concurrent)中提供了一系列的原子操作类,这些类基于CAS(Compare-And-Swap)机制实现,能够在不使用锁的情况下保证操作的原子性。本文将全面总结Java中的Atomic原子类家族。

1. Atomic原子类概述

1.1 什么是Atomic原子类

Atomic原子类是Java并发包中提供的一组线程安全的操作类,它们能够在多线程环境下保证对变量操作的原子性,而无需使用传统的锁机制。Atomic原子类的核心特性是原子性、可见性和有序性,这些特性使得它们在高并发场景下比传统锁机制具有更好的性能。

1.2 Atomic原子类的实现原理

Atomic原子类的实现基于CAS(Compare-And-Swap)机制,这是一种无锁算法。CAS操作包含三个操作数:

  • 内存位置V
  • 预期值A
  • 新值B

当且仅当内存位置V的值等于预期值A时,才会将该内存位置的值更新为新值B,否则不做任何操作。整个比较并交换的操作是原子的,由CPU硬件指令保证。关于CAS机制的详细解释,可参考《CAS机制详解》。

在Java中,Atomic原子类内部通过调用sun.misc.Unsafe类提供的CAS相关方法实现原子操作。Unsafe类提供了直接操作内存的能力,并通过JNI调用底层的CPU指令来实现CAS操作。

2. Atomic原子类的分类

Java中的Atomic原子类可以分为四大类:

  1. 基本类型原子类:用于操作基本数据类型的原子类
  2. 数组类型原子类:用于操作数组中元素的原子类
  3. 引用类型原子类:用于操作引用类型的原子类
  4. 字段更新器:用于原子地更新对象的字段

下面我们将逐一介绍这些类型的原子类。

3. 基本类型原子类

基本类型原子类主要包括以下几个:

  • AtomicBoolean:原子操作布尔类型
  • AtomicInteger:原子操作整型
  • AtomicLong:原子操作长整型

这些类提供的方法类似,下面以AtomicInteger为例介绍其主要方法和使用方式。

3.1 AtomicInteger的主要方法

// 创建AtomicInteger实例,初始值为0
AtomicInteger ai = new AtomicInteger();

// 创建AtomicInteger实例,指定初始值
AtomicInteger ai = new AtomicInteger(10);

// 获取当前值
int currentValue = ai.get();

// 设置新值
ai.set(20);

// 原子地设置新值并返回旧值
int oldValue = ai.getAndSet(30);

// 原子地将当前值增加1并返回旧值
int oldValue = ai.getAndIncrement();

// 原子地将当前值增加1并返回新值
int newValue = ai.incrementAndGet();

// 原子地将当前值减少1并返回旧值
int oldValue = ai.getAndDecrement();

// 原子地将当前值减少1并返回新值
int newValue = ai.decrementAndGet();

// 原子地将给定的值加到当前值上并返回旧值
int oldValue = ai.getAndAdd(5);

// 原子地将给定的值加到当前值上并返回新值
int newValue = ai.addAndGet(5);

// 原子地更新值,使用给定的更新函数
int updatedValue = ai.updateAndGet(x -> x * 2);

// 原子地更新值,使用给定的更新函数并返回旧值
int oldValue = ai.getAndUpdate(x -> x * 2);

// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数
int updatedValue = ai.accumulateAndGet(10, (x, y) -> x + y);

// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数并返回旧值
int oldValue = ai.getAndAccumulate(10, (x, y) -> x + y);

3.2 使用示例

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    private static final AtomicInteger counter = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        // 创建10个线程,每个线程对计数器增加1000次
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.incrementAndGet();
                }
            });
            threads[i].start();
        }
        
        // 等待所有线程执行完成
        for (Thread thread : threads) {
            thread.join();
        }
        
        // 输出最终结果
        System.out.println("Final counter value: " + counter.get()); // 应该输出10000
    }
}

4. 数组类型原子类

数组类型原子类主要包括以下几个:

  • AtomicIntegerArray:原子操作整型数组
  • AtomicLongArray:原子操作长整型数组
  • AtomicReferenceArray:原子操作引用类型数组

这些类提供的方法类似,下面以AtomicIntegerArray为例介绍其主要方法和使用方式。

4.1 AtomicIntegerArray的主要方法

// 创建指定长度的AtomicIntegerArray实例,所有元素初始值为0
AtomicIntegerArray array = new AtomicIntegerArray(10);

// 创建AtomicIntegerArray实例,使用给定的int数组初始化
int[] intArray = {1, 2, 3, 4, 5};
AtomicIntegerArray array = new AtomicIntegerArray(intArray);

// 获取指定索引位置的元素值
int value = array.get(0);

// 设置指定索引位置的元素值
array.set(0, 10);

// 原子地设置指定索引位置的元素值并返回旧值
int oldValue = array.getAndSet(0, 20);

// 原子地将指定索引位置的元素值增加1并返回旧值
int oldValue = array.getAndIncrement(0);

// 原子地将指定索引位置的元素值增加1并返回新值
int newValue = array.incrementAndGet(0);

// 原子地将指定索引位置的元素值减少1并返回旧值
int oldValue = array.getAndDecrement(0);

// 原子地将指定索引位置的元素值减少1并返回新值
int newValue = array.decrementAndGet(0);

// 原子地将给定的值加到指定索引位置的元素值上并返回旧值
int oldValue = array.getAndAdd(0, 5);

// 原子地将给定的值加到指定索引位置的元素值上并返回新值
int newValue = array.addAndGet(0, 5);

// 原子地更新指定索引位置的元素值,使用给定的更新函数
int updatedValue = array.updateAndGet(0, x -> x * 2);

// 原子地更新指定索引位置的元素值,使用给定的更新函数并返回旧值
int oldValue = array.getAndUpdate(0, x -> x * 2);

// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数
int updatedValue = array.accumulateAndGet(0, 10, (x, y) -> x + y);

// 原子地将当前值与输入值进行计算并更新,使用给定的累加器函数并返回旧值
int oldValue = array.getAndAccumulate(0, 10, (x, y) -> x + y);

4.2 使用示例

import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayExample {
    private static final AtomicIntegerArray array = new AtomicIntegerArray(5);
    
    public static void main(String[] args) throws InterruptedException {
        // 创建5个线程,每个线程操作数组中的一个元素
        Thread[] threads = new Thread[5];
        for (int i = 0; i < threads.length; i++) {
            final int index = i;
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    array.incrementAndGet(index);
                }
            });
            threads[i].start();
        }
        
        // 等待所有线程执行完成
        for (Thread thread : threads) {
            thread.join();
        }
        
        // 输出数组的最终状态
        System.out.print("Final array values: ");
        for (int i = 0; i < array.length(); i++) {
            System.out.print(array.get(i) + " "); // 每个元素都应该是1000
        }
    }
}

5. 引用类型原子类

引用类型原子类主要包括以下几个:

  • AtomicReference:原子操作引用类型
  • AtomicStampedReference:带版本戳的原子引用类型,解决ABA问题
  • AtomicMarkableReference:带标记位的原子引用类型

5.1 AtomicReference

AtomicReference用于原子地更新引用类型变量。它的使用方式与基本类型原子类类似:

// 创建AtomicReference实例,指定初始引用
AtomicReference<User> atomicUser = new AtomicReference<>(new User("Alice", 25));

// 获取当前引用
User currentUser = atomicUser.get();

// 设置新引用
atomicUser.set(new User("Bob", 30));

// 原子地设置新引用并返回旧引用
User oldUser = atomicUser.getAndSet(new User("Charlie", 35));

// 原子地比较并设置引用
boolean updated = atomicUser.compareAndSet(
    new User("Charlie", 35), 
    new User("David", 40)
);

// 原子地更新引用,使用给定的更新函数
User updatedUser = atomicUser.updateAndGet(user -> new User(user.getName(), user.getAge() + 1));

// 原子地更新引用,使用给定的更新函数并返回旧引用
User oldUser = atomicUser.getAndUpdate(user -> new User(user.getName(), user.getAge() + 1));

// 原子地将当前引用与输入引用进行计算并更新,使用给定的累加器函数
User updatedUser = atomicUser.accumulateAndGet(
    new User("Eve", 1), 
    (u1, u2) -> new User(u1.getName(), u1.getAge() + u2.getAge())
);

// 原子地将当前引用与输入引用进行计算并更新,使用给定的累加器函数并返回旧引用
User oldUser = atomicUser.getAndAccumulate(
    new User("Frank", 1), 
    (u1, u2) -> new User(u1.getName(), u1.getAge() + u2.getAge())
);

// 自定义User类
class User {
    private String name;
    private int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

5.2 AtomicStampedReference

AtomicStampedReference通过引入版本戳解决了ABA问题。ABA问题是指在CAS操作中,一个值从A变成B,又从B变回A,而CAS操作无法检测到这个变化。

// 创建AtomicStampedReference实例,指定初始引用和初始版本戳
AtomicStampedReference<String> atomicStampedRef = new AtomicStampedReference<>("initial", 0);

// 获取当前引用和版本戳
int[] stampHolder = new int[1];
String currentRef = atomicStampedRef.get(stampHolder);
int currentStamp = stampHolder[0];

// 原子地比较并设置引用和版本戳
boolean updated = atomicStampedRef.compareAndSet(
    "initial", 
    "updated", 
    currentStamp, 
    currentStamp + 1
);

// 设置新引用和版本戳(不进行比较)
atomicStampedRef.set("newValue", 10);

// 尝试原子地更新引用和版本戳
boolean updated = atomicStampedRef.attemptStamp("newValue", 11);

5.3 AtomicMarkableReference

AtomicMarkableReferenceAtomicStampedReference类似,但它不是使用整数作为版本戳,而是使用布尔值作为标记位:

// 创建AtomicMarkableReference实例,指定初始引用和初始标记
AtomicMarkableReference<String> atomicMarkableRef = new AtomicMarkableReference<>("initial", false);

// 获取当前引用和标记
boolean[] markHolder = new boolean[1];
String currentRef = atomicMarkableRef.get(markHolder);
boolean currentMark = markHolder[0];

// 原子地比较并设置引用和标记
boolean updated = atomicMarkableRef.compareAndSet(
    "initial", 
    "updated", 
    currentMark, 
    !currentMark
);

// 设置新引用和标记(不进行比较)
atomicMarkableRef.set("newValue", true);

6. 字段更新器

字段更新器允许我们在不修改原有类的情况下,对其字段进行原子操作。主要包括以下几个:

  • AtomicIntegerFieldUpdater:原子更新整型字段
  • AtomicLongFieldUpdater:原子更新长整型字段
  • AtomicReferenceFieldUpdater:原子更新引用类型字段

6.1 AtomicIntegerFieldUpdater

使用AtomicIntegerFieldUpdater需要注意以下几点:

  • 被更新的字段必须是volatile类型的,以确保可见性
  • 字段的访问权限必须是publicprotected或默认的(包可见性)
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterExample {
    public static void main(String[] args) {
        // 创建Person实例
        Person person = new Person("Alice", 25);
        
        // 创建字段更新器
        AtomicIntegerFieldUpdater<Person> ageUpdater = AtomicIntegerFieldUpdater.newUpdater(Person.class, "age");
        
        // 原子地增加年龄
        ageUpdater.incrementAndGet(person);
        System.out.println("Updated age: " + person.getAge()); // 输出26
        
        // 原子地比较并设置年龄
        boolean updated = ageUpdater.compareAndSet(person, 26, 30);
        System.out.println("Update successful: " + updated); // 输出true
        System.out.println("New age: " + person.getAge()); // 输出30
    }
    
    static class Person {
        private String name;
        volatile int age; // 必须是volatile类型
        
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        public String getName() {
            return name;
        }
        
        public int getAge() {
            return age;
        }
    }
}

7. Atomic原子类的优缺点

7.1 优点

  1. 高性能:相比传统的锁机制,Atomic原子类基于CAS实现,避免了线程切换和阻塞的开销,具有更高的性能
  2. 非阻塞:线程在竞争失败时不会被挂起,而是可以立即尝试再次操作
  3. 简单易用:API设计简洁明了,使用方便
  4. 无需手动释放锁:不存在死锁风险

7.2 缺点

  1. ABA问题:基本的Atomic原子类存在ABA问题(可以使用AtomicStampedReferenceAtomicMarkableReference解决)
  2. 自旋开销:在高并发场景下,大量线程同时竞争同一个原子变量,会导致CAS操作失败率增加,线程不断自旋尝试,增加CPU开销
  3. 功能有限:Atomic原子类只提供了简单的原子操作,对于复杂的复合操作,需要额外的处理

8. Atomic原子类的使用场景

Atomic原子类适用于以下场景:

  1. 计数器:如统计网站访问量、接口调用次数等
  2. 并发标记:如标记某个资源是否被占用
  3. 线程安全的单例模式:可以使用AtomicReference实现双重检查锁定的单例模式
  4. 无锁算法实现:如无锁队列、无锁栈等
  5. 并发环境下的状态管理:如状态机的实现

9. 总结

本文全面总结了Java中的Atomic原子类家族,包括基本类型原子类、数组类型原子类、引用类型原子类和字段更新器。这些原子类基于CAS机制实现,能够在不使用锁的情况下保证操作的原子性,在高并发场景下具有更好的性能。

Atomic原子类是Java并发编程中的重要工具,合理使用它们可以帮助我们编写高性能、线程安全的并发程序。但同时也需要注意它们的局限性,如ABA问题和自旋开销等,在实际应用中根据具体场景选择合适的并发控制机制。


文章作者: lucky845
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 lucky845 !
评论
  目录