原子类
字数: 0 字 时长: 0 分钟
在并发编程中,共享变量的操作需要保证原子性,避免竞态条件(Race Condition
)。传统方式是采用 synchronized
或 Lock
来保证并发安全。
但是这些阻塞式同步操作会带来:
- 上下文切换、线程阻塞的性能开销
- 不恰当的加锁顺序有死锁风险
- 锁粒度粗,有时需要对整个大对象加锁去保护一个小的字段
java.util.concurrent
包基于 CAS (Compare-And-Swap
)提供了原子类,这种无锁乐观并发控制性能更好。
基本原子类
AtomicInteger
(最常用)AtomicLong
AtomicBoolean
常用 API
java
// 获取当前值
public final int get()
// 获取当前的值,并设置新的值
public final int getAndSet(int newValue)
// 获取当前的值并自增
public final int getAndIncrement()
// 获取当前的值并自减
public final int getAndDecrement()
// 获取当前的值并加上预期的值
public final int getAndAdd(int delta)
// CAS 输入值等于预期值,则以原子方式将新值设置入目标值
public final boolean compareAndSet(int expect, int update)
使用 AtomicInteger
保证原子性代码案例
50 个线程并行执行,每个线程对共享变量进行 1000 次累加操作
java
class MyNumber //资源类
{
AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus() {
atomicInteger.getAndIncrement();
}
}
public class AtomicIntegerDemo {
public static final int SIZE = 50;
public static void main(String[] args) {
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 0; i < SIZE; i++) {
new Thread(() -> {
try {
for (int j = 1;j <= 1000; j++) {
myNumber.addPlusPlus();
}
}finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
// 采用 countDownLatch 等待所有线程执行完再执行主线程
countDownLatch.await();
// 输出 50000
System.out.println(myNumber.atomicInteger.get());
}
}
数组原子类
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray<T>
数组原子类内部维护一个 final
的数组,通过 Unsafe
类直接操作数组内存。操作元素时通过 volatile
确保可见性,结合 CAS 保证原子性。
JDK21 源码
Unsafe
类已被VarHandle
替代
引用类型原子类
AtomicReference
原子引用类 CAS 解决线程安全
java
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User z3 = new User("z3", 22);
User li4 = new User("li4", 23);
atomicReference.set(z3);
// 此时 atomicReference.get() 获取到的是 User(userName=z3, age=22)
System.out.println(atomicReference.get().toString());
//true---User(userName=li4, age=23) 第一次 CAS 修改 z3 为 li4 成功
System.out.println(atomicReference.compareAndSet(z3,li4)
+ "---" + atomicReference.get().toString());
//false---User(userName=li4, age=23)
// 第二次 CAS 操作,发现 z3 已经被修改为 li4,而期望值是 z3 的值,所以操作失败
System.out.println(atomicReference.compareAndSet(z3,li4)
+ "---" + atomicReference.get().toString());
}
AtomicStampedReference
带戳记流水的原子引用类,标记版本号,可以解决 ABA 修改过几次的问题。
javaBook -> mySql -> javaBook 模拟 A B A 问题
java
@Data
@AllArgsConstructor
class Book{
private int id;
private String bookName;
}
public class AtomicStampedDemo {
public static void main(String[] args) {
Book javaBook = new Book(1, "javaBook");
// 赋初值 第一本书为 javaBook
AtomicStampedReference<Book> stampedReference =
new AtomicStampedReference<>(javaBook, 1);
Book mysqlBook = new Book(2, "mysqlBook");
// 记录 CAS 操作是否成功
boolean b;
// 第一次 CAS 操作 把 javaBook 改为 mysqlBook
b = stampedReference.compareAndSet(javaBook //预期值
, mysqlBook //更新值
, stampedReference.getStamp() //预期版本号
, stampedReference.getStamp() + 1); //更新版本号
// true Book(id=2, bookName=mysqlBook) 2
System.out.println(b + " " +stampedReference.getReference()
+ " " + stampedReference.getStamp());
// 第二次 CAS 操作 把 mysqlBook 改回 javaBook
b = stampedReference.compareAndSet(mysqlBook
, javaBook
, stampedReference.getStamp()
, stampedReference.getStamp() + 1);
// true Book(id=1, bookName=javaBook) 3
System.out.println(b + " " +stampedReference.getReference()
+ " " + stampedReference.getStamp());
}
}
AtomicMarkableReference
原子标记引用类型,解决 ABA 是否被修改过 的问题
java
public class AtomicMarkableReferenceDemo {
// 初始化 marked 为 false
static AtomicMarkableReference markableReference =
new AtomicMarkableReference(100,false);
public static void main(String[] args) {
new Thread(()->{
markableReference.compareAndSet(100 // CAS 预期值
,1000 // CAS 更新值
,false // 预期 false
,!marked); // 新标记 true
},"t1").start();
new Thread(()->{
// 因为 markableReference 的标记已经被 t1 更新为 true,这里 CAS 操作会失败
markableReference.compareAndSet(100, 2000, false, !marked);
},"t2").start();
}
}
对象属性修改原子类
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
- 传统方式使用
synchronized
对转账操作进行线程安全处理粒度太粗,是对象锁 - 使用
AtomicIntegerFieldupdater
对金额变量进行细粒度加锁,更轻量化,性能更高
java
class BankAccount {
String bankName = "CCB";
public volatile int money = 0;
// 1. 传统方式
public synchronized void add() {
money++;
}
// 2. 使用 AtomicIntegerFieldupdater 对 money 字段细粒度加锁
public void transMoney(BankAccount bankAccount) {
fieldUpdater.getAndIncrement(bankAccount);
}
AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater
.newUpdater(BankAccount.class, "money");
}
public static void main(String[] args) throws Exception{
BankAccount bankAccount = new BankAccount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
for (int j = 0; j < 10000; j++) {
// bankAccount.add();
bankAccount.transMoney(bankAccount);
}
}finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
}
- 使用
AtomicReferenceFieldUpdater
引用类型属性进行细粒度加锁
java
import java.util.concurrent.TimeUnit;
class MyVar { //资源类
public volatile Boolean isInit = Boolean.FALSE;
AtomicReferenceFieldUpdater<MyVar, Boolean> referenceFieldUpdater =
AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
public void init(MyVar myVar) {
if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
System.out.println(Thread.currentThread().getName() + "初始化开始,需要 2 秒");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "初始化结束");
} else {
System.out.println(Thread.currentThread().getName() + "已经有其他线程在初始化了");
}
}
}
/**
* 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作
要求只能被初始化一次,只有一个线程操作成功
*/
public static void main(String[] args) {
MyVar myVar = new MyVar();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
myVar.init(myVar);
}, "t" + i).start();
}
}
//t2已经有其他线程在初始化了
//t1初始化开始,需要 2 秒
//t0已经有其他线程在初始化了
//t4已经有其他线程在初始化了
//t3已经有其他线程在初始化了
//t1初始化结束
原子增强类
- LongAdder
高并发下,比 AtomicLong
减少乐观锁重试次数,性能更高,但耗费空间也更大;低并发下和 AtomicLong
性能差不多
- LongAccumulator
LongAdder
只能从0
开始累加;LongAccumulator
可以指定一个初始值与计算逻辑
java
LongAdder longAdder = new LongAdder();
public void addLongAdder() {
longAdder.increment();
}
LongAccumulator longAccumulator = new LongAccumulator((x,y)->x+y,0);
public void addLongAccumulator() {
longAccumulator.accumulate(1);
}
LongAdder 原理
LongAdder 的基本原理是 分散热点 ,将 Value 值分散到一个 Cell 数组中,不同线程会命中到不同的 Cell 中,减少冲突自旋概率,从而提高并发性能
总结:
- 优先 LongAddr:写入远多于读取的高并发场景
- 优先 AtomicLong:需要频繁读取当前值且对一致性要求较高