跳到主要内容

Java 原子类详解

原子类是Java并发包中提供的一组线程安全的工具类,它们基于CAS(Compare-And-Swap)操作实现,无需使用synchronized关键字。本文将详细介绍Java原子类的原理、使用方法和最佳实践。

核心价值

原子类 = 无锁并发 + 高性能 + 线程安全性 + 简洁API + 内存可见性

  • 🔄 无锁操作:基于CAS机制,避免传统锁带来的上下文切换开销
  • 🚀 高性能:在低竞争环境下性能优于传统同步方式
  • 🛡️ 线程安全:保证操作的原子性,避免并发问题
  • 📊 精细控制:支持复杂的原子性条件更新操作
  • 🔍 内存可见性:保证多线程间的数据一致性

1. 原子类概述

1.1 什么是原子类?

核心概念

原子类是基于CAS(Compare-And-Swap)操作实现的线程安全工具类,它们提供原子性的读写操作,无需使用传统的同步机制,具有高性能、无阻塞的特点。

1.2 CAS机制原理

CAS (Compare-And-Swap) 工作原理

CAS是一种无锁算法,其核心是一条CPU原子指令,执行过程如下:

  1. 比较(Compare):读取内存中当前值并与期望值比较
  2. 交换(Swap):如果当前值等于期望值,则将值更新为新值;否则不更新
  3. 返回结果:返回比较结果,表明操作是否成功

CAS操作包含三个参数:

  • 内存位置V (Variable):要更新的变量
  • 期望值A (Expected):预期当前值
  • 新值B (New):要设置的新值

CAS操作保证了原子性:要么完全成功,要么完全失败,不会出现中间状态。

java
1public boolean compareAndSet(AtomicInteger value, int expect, int update) {
2 // 这个方法是一个模拟,实际上CAS是由CPU原子指令实现的
3 // 1. 读取当前值
4 int current = value.get();
5
6 // 2. 比较当前值与期望值
7 if (current == expect) {
8 // 3. 如果相等,则更新为新值
9 value.set(update);
10 return true;
11 } else {
12 // 4. 如果不相等,则更新失败
13 return false;
14 }
15}
16
17// 使用CAS实现自旋锁
18public void incrementUsingCAS(AtomicInteger value) {
19 int oldValue;
20 do {
21 // 读取当前值
22 oldValue = value.get();
23 // 尝试CAS操作,直到成功
24 } while (!value.compareAndSet(oldValue, oldValue + 1));
25}

1.3 原子类分类

Java原子类家族

包含类:

  • AtomicInteger:原子整型,提供对int值的原子操作
  • AtomicLong:原子长整型,提供对long值的原子操作
  • AtomicBoolean:原子布尔型,提供对boolean值的原子操作

常用场景:

  • 计数器
  • 生成序列号
  • 标志位控制
  • 状态切换

2. 基本类型原子类

2.1 AtomicInteger

AtomicInteger核心API

方法描述等价同步代码
get()获取当前值return value;
set(int)设置新值value = newValue;
getAndSet(int)设置新值并返回旧值int old = value; value = newValue; return old;
compareAndSet(int, int)比较并设置if(value==expect){value=update; return true;} else return false;
getAndIncrement()递增并返回旧值int old = value; value++; return old;
getAndDecrement()递减并返回旧值int old = value; value--; return old;
incrementAndGet()递增并返回新值return ++value;
decrementAndGet()递减并返回新值return --value;
getAndAdd(int)加上指定值并返回旧值int old = value; value += delta; return old;
addAndGet(int)加上指定值并返回新值value += delta; return value;
updateAndGet(IntUnaryOp)应用函数并返回新值value = updateFunction(value); return value;
getAndUpdate(IntUnaryOp)应用函数并返回旧值int old = value; value = updateFunction(value); return old;
java
1import java.util.concurrent.atomic.AtomicInteger;
2
3// 创建AtomicInteger
4AtomicInteger counter = new AtomicInteger(); // 默认值为0
5AtomicInteger initializedCounter = new AtomicInteger(100); // 初始值为100
6
7// 获取当前值
8int current = counter.get(); // 0
9
10// 设置值
11counter.set(50);
12System.out.println(counter.get()); // 50
13
14// 原子递增/递减
15int prev = counter.getAndIncrement(); // 返回50,counter变为51
16int next = counter.incrementAndGet(); // counter变为52,返回52
17
18int prev2 = counter.getAndDecrement(); // 返回52,counter变为51
19int next2 = counter.decrementAndGet(); // counter变为50,返回50
20
21// 原子加法/减法
22int beforeAdd = counter.getAndAdd(15); // 返回50,counter变为65
23int afterAdd = counter.addAndGet(10); // counter变为75,返回75
24
25// 比较并设置
26boolean success = counter.compareAndSet(75, 100); // 如果当前值是75,则设为100
27System.out.println(success + ", 当前值: " + counter.get()); // true, 当前值: 100

2.2 AtomicLong

AtomicLong特性

核心特点:

  • 提供对long类型变量的原子操作
  • 64位原子操作,解决在32位系统上long/double操作非原子性问题
  • JDK 8后内部优化使用了CPU的CAS指令
  • 高并发场景可考虑使用LongAdder替代以提高性能

应用场景:

  • 全局序列号生成
  • 大数量计数
  • 高精度统计
  • ID生成器
java
1import java.util.concurrent.atomic.AtomicLong;
2
3// 创建AtomicLong
4AtomicLong counter = new AtomicLong(0); // 初始值为0
5AtomicLong idGenerator = new AtomicLong(1000); // 初始值为1000
6
7// 基本操作
8long currentValue = counter.get(); // 获取当前值
9counter.set(100); // 设置新值
10
11// 原子增减
12long oldValue = counter.getAndIncrement(); // 返回0, counter变为1
13long newValue = counter.incrementAndGet(); // counter变为2, 返回2
14
15// 原子更新
16oldValue = counter.getAndAdd(10); // 返回2, counter变为12
17newValue = counter.addAndGet(8); // counter变为20, 返回20
18
19// CAS操作
20boolean updated = counter.compareAndSet(20, 30); // true, counter变为30

2.3 AtomicBoolean

AtomicBoolean特性与应用

主要特点:

  • 原子性地更新boolean类型值
  • 适合状态标志和开关控制场景
  • 线程安全的初始化标记
  • 原子开关操作

常用方法:

  • get(): 获取当前值
  • set(boolean): 设置新值
  • getAndSet(boolean): 设置新值并返回旧值
  • compareAndSet(boolean, boolean): 比较并设置

典型应用场景:

  • 一次性操作标志
  • 服务开关控制
  • 线程安全的初始化
  • 状态标记
java
1import java.util.concurrent.atomic.AtomicBoolean;
2
3// 创建AtomicBoolean,默认false
4AtomicBoolean flag = new AtomicBoolean();
5
6// 创建带初始值的AtomicBoolean
7AtomicBoolean enabledFlag = new AtomicBoolean(true);
8
9// 获取当前值
10boolean isEnabled = enabledFlag.get(); // true
11
12// 设置新值
13enabledFlag.set(false);
14
15// 原子地获取旧值并设置新值
16boolean oldValue = enabledFlag.getAndSet(true);
17System.out.println("旧值: " + oldValue + ", 新值: " + enabledFlag.get());
18// 输出: 旧值: false, 新值: true
19
20// CAS操作
21boolean wasUpdated = enabledFlag.compareAndSet(true, false);
22System.out.println("更新成功: " + wasUpdated + ", 当前值: " + enabledFlag.get());
23// 输出: 更新成功: true, 当前值: false

3. 引用类型原子类

引用类型原子类概述

引用类型原子类用于原子性地更新对象引用,主要包括以下几个类:

  • AtomicReference:原子更新一个对象引用
  • AtomicStampedReference:带有版本号的原子引用,解决ABA问题
  • AtomicMarkableReference:带有标记的原子引用,用于标记引用是否被更新过

这些类使得在不使用锁的情况下,安全地更新对象引用成为可能,特别适合于实现无锁数据结构和算法。

3.1 AtomicReference

java
1import java.util.concurrent.atomic.AtomicReference;
2
3// 创建带初始值的AtomicReference
4AtomicReference<String> atomicString = new AtomicReference<>("初始值");
5
6// 创建空的AtomicReference
7AtomicReference<User> atomicUser = new AtomicReference<>();
8
9// 获取引用
10String value = atomicString.get();
11System.out.println("当前值: " + value); // 当前值: 初始值
12
13// 设置新值
14atomicString.set("新值");
15System.out.println("更新后: " + atomicString.get()); // 更新后: 新值
16
17// 原子方式获取并设置
18String oldValue = atomicString.getAndSet("更新的值");
19System.out.println("旧值: " + oldValue + ", 当前值: " + atomicString.get());
20// 输出: 旧值: 新值, 当前值: 更新的值
21
22// 比较并设置
23boolean wasUpdated = atomicString.compareAndSet("更新的值", "最终值");
24System.out.println("更新是否成功: " + wasUpdated + ", 当前值: " + atomicString.get());
25// 输出: 更新是否成功: true, 当前值: 最终值
26
27// 使用函数更新
28atomicString.updateAndGet(current -> current + " - 附加内容");
29System.out.println("函数更新后: " + atomicString.get());
30// 输出: 函数更新后: 最终值 - 附加内容

3.2 AtomicStampedReference

ABA 问题与解决方案

ABA问题是指:

  • 线程1读取共享变量的值为A
  • 线程2将共享变量的值修改为B,然后又修改回A
  • 线程1进行CAS操作,发现共享变量的值仍为A,认为没有被修改过,但实际上已经经历了 A→B→A 的变化

AtomicStampedReference通过添加版本号解决ABA问题:

  • 每次更新引用的同时更新版本号
  • CAS操作同时检查引用和版本号
  • 即使引用值相同,若版本号不同,CAS操作也会失败
java
1import java.util.concurrent.atomic.AtomicStampedReference;
2
3// 创建AtomicStampedReference,初始引用为"A",初始版本号为1
4AtomicStampedReference<String> asr =
5 new AtomicStampedReference<>("A", 1);
6
7// 读取当前引用和版本号
8int[] stampHolder = new int[1];
9String value = asr.get(stampHolder);
10int stamp = stampHolder[0];
11
12System.out.println("初始值: " + value + ", 版本号: " + stamp);
13
14// 更新引用和版本号
15boolean success = asr.compareAndSet("A", "B", stamp, stamp + 1);
16System.out.println("更新结果: " + success);
17
18// 再次获取新的引用和版本号
19value = asr.get(stampHolder);
20stamp = stampHolder[0];
21System.out.println("更新后值: " + value + ", 版本号: " + stamp);
22
23// 尝试使用过期版本号更新,将会失败
24boolean outdatedSuccess = asr.compareAndSet("B", "C", 1, 2);
25System.out.println("使用旧版本号更新结果: " + outdatedSuccess);
26
27// 使用当前版本号更新,将会成功
28boolean currentSuccess = asr.compareAndSet("B", "C", stamp, stamp + 1);
29System.out.println("使用当前版本号更新结果: " + currentSuccess);
30System.out.println("最终值: " + asr.getReference() + ", 版本号: " + asr.getStamp());

3.3 AtomicMarkableReference

AtomicMarkableReference与AtomicStampedReference的对比

特性AtomicMarkableReferenceAtomicStampedReference
标记类型布尔值(标记/未标记)整数(版本号)
内存占用较小较大
适用场景只需要标记对象是否被修改过需要完整的版本历史
解决ABA部分解决(只能检测到是否有过修改)完全解决(通过版本号区分)
API复杂度较简单较复杂
java
1import java.util.concurrent.atomic.AtomicMarkableReference;
2
3// 创建AtomicMarkableReference,初始引用为"数据",初始标记为false
4AtomicMarkableReference<String> amr =
5 new AtomicMarkableReference<>("数据", false);
6
7// 获取当前值和标记
8boolean[] markHolder = new boolean[1];
9String value = amr.get(markHolder);
10boolean mark = markHolder[0];
11
12System.out.println("初始值: " + value + ", 标记: " + mark);
13
14// 更新引用并设置标记
15boolean success = amr.compareAndSet("数据", "新数据", false, true);
16System.out.println("更新结果: " + success);
17
18// 再次获取值和标记
19value = amr.get(markHolder);
20mark = markHolder[0];
21System.out.println("更新后值: " + value + ", 标记: " + mark);
22
23// 仅更新标记,不更新引用
24success = amr.attemptMark("新数据", false);
25System.out.println("仅更新标记结果: " + success);
26System.out.println("标记更新后: " + amr.isMarked());

4. 数组原子类

数组原子类概述

数组原子类提供对数组元素的原子操作,主要包括:

  • AtomicIntegerArray:原子更新整型数组里的元素
  • AtomicLongArray:原子更新长整型数组里的元素
  • AtomicReferenceArray:原子更新引用类型数组里的元素

数组原子类保证对数组中每个元素的操作都是原子性的,但不保证对整个数组的操作是原子性的。

4.1 AtomicIntegerArray

java
1import java.util.concurrent.atomic.AtomicIntegerArray;
2
3// 创建AtomicIntegerArray
4AtomicIntegerArray array = new AtomicIntegerArray(5); // 默认值为0
5AtomicIntegerArray initializedArray = new AtomicIntegerArray(new int[]{1, 2, 3, 4, 5}); // 初始值
6
7// 获取元素
8int value = array.get(0); // 0
9System.out.println("array[0]: " + value);
10
11// 设置元素
12array.set(0, 10);
13System.out.println("array[0] after set: " + array.get(0)); // 10
14
15// 原子递增/递减
16int prev = array.getAndIncrement(1); // 返回2, array[1]变为3
17int next = array.incrementAndGet(1); // array[1]变为4, 返回4
18
19prev = array.getAndDecrement(1); // 返回4, array[1]变为3
20next = array.decrementAndGet(1); // array[1]变为2, 返回2
21
22// 原子加法/减法
23prev = array.getAndAdd(2, 15); // 返回3, array[2]变为18
24next = array.addAndGet(2, 10); // array[2]变为28, 返回28
25
26// 比较并设置
27boolean success = array.compareAndSet(2, 28, 30); // 如果当前值是28,则设为30
28System.out.println("CAS成功: " + success + ", array[2]: " + array.get(2)); // true, array[2]: 30

5. 字段更新器

5.1 字段更新器概述

字段更新器允许原子性地更新对象的volatile字段,无需将整个对象声明为原子类。

java
1import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
2import java.util.concurrent.atomic.AtomicLongFieldUpdater;
3import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
4
5// 假设有一个类需要原子更新其int字段
6public class AtomicFieldUpdaterExample {
7 public static class Counter {
8 public volatile int count = 0;
9
10 // 使用AtomicIntegerFieldUpdater
11 private static final AtomicIntegerFieldUpdater<Counter> UPDATER =
12 AtomicIntegerFieldUpdater.newUpdater(Counter.class, "count");
13
14 public void increment() {
15 UPDATER.incrementAndGet(this);
16 }
17
18 public int getCount() {
19 return UPDATER.get(this);
20 }
21
22 public boolean compareAndSet(int expect, int update) {
23 return UPDATER.compareAndSet(this, expect, update);
24 }
25 }
26
27 public static void main(String[] args) {
28 Counter counter = new Counter();
29
30 // 多个线程同时递增计数器
31 for (int i = 0; i < 10; i++) {
32 new Thread(() -> {
33 for (int j = 0; j < 1000; j++) {
34 counter.increment();
35 }
36 }).start();
37 }
38
39 try {
40 Thread.sleep(1000); // 等待所有线程完成
41 } catch (InterruptedException e) {
42 Thread.currentThread().interrupt();
43 }
44
45 System.out.println("最终计数: " + counter.getCount());
46 }
47}

6. 原子类最佳实践

6.1 使用建议

核心原则

使用原子类时需要考虑以下因素:

  • 性能要求:原子类适合高并发、低竞争场景
  • 功能需求:根据具体需求选择合适的原子类
  • ABA问题:需要版本控制时使用AtomicStampedReference
  • 内存开销:原子类比普通变量占用更多内存

6.2 性能优化

java
1import java.util.concurrent.atomic.AtomicInteger;
2
3public class AtomicClassOptimization {
4 private final AtomicInteger counter = new AtomicInteger(0);
5
6 /**
7 * 减少CAS失败
8 */
9 public void optimizedIncrement() {
10 while (true) {
11 int current = counter.get();
12 int next = current + 1;
13 if (counter.compareAndSet(current, next)) {
14 break;
15 }
16 // 可以添加退避策略
17 Thread.yield();
18 }
19 }
20
21 /**
22 * 避免过度使用
23 */
24 public static class AvoidOveruse {
25 // 不推荐:过度使用原子类
26 private final AtomicInteger x = new AtomicInteger(0);
27 private final AtomicInteger y = new AtomicInteger(0);
28 private final AtomicInteger z = new AtomicInteger(0);
29
30 // 推荐:使用复合对象
31 public static class Point {
32 private volatile int x, y, z;
33
34 public synchronized void setCoordinates(int x, int y, int z) {
35 this.x = x;
36 this.y = y;
37 this.z = z;
38 }
39
40 public synchronized int[] getCoordinates() {
41 return new int[]{x, y, z};
42 }
43 }
44 }
45}

7. 总结

原子类是Java并发编程中的重要工具,它们提供了高性能、无阻塞的线程安全操作。

7.1 关键要点

  1. CAS机制:比较并交换,无锁算法的基础
  2. 原子类分类:基本类型、引用类型、数组类型、字段更新器
  3. ABA问题:通过版本号或标记解决
  4. 性能优化:减少CAS失败、避免过度使用

7.2 选择建议

场景推荐原子类原因
简单计数器AtomicInteger/AtomicLong性能高,使用简单
对象引用AtomicReference支持泛型,功能丰富
需要版本控制AtomicStampedReference解决ABA问题
需要标记AtomicMarkableReference支持布尔标记
数组操作AtomicIntegerArray等原子数组操作
字段更新字段更新器无需修改类结构

7.3 学习建议

  1. 理解原理:深入理解CAS机制的工作原理
  2. 实践验证:通过编写代码验证不同原子类的效果
  3. 性能测试:对比原子类与同步机制的性能差异
  4. 场景选择:根据具体需求选择合适的原子类

通过深入理解和熟练运用这些原子类技术,我们能够构建出更加高效、健壮和可维护的Java并发应用程序。

评论