Skip to content

原子类

字数: 0 字 时长: 0 分钟

在并发编程中,共享变量的操作需要保证原子性,避免竞态条件(Race Condition)。传统方式是采用 synchronizedLock 来保证并发安全。

但是这些阻塞式同步操作会带来:

  • 上下文切换、线程阻塞的性能开销
  • 不恰当的加锁顺序有死锁风险
  • 锁粒度粗,有时需要对整个大对象加锁去保护一个小的字段

java.util.concurrent基于 CAS (Compare-And-Swap)提供了原子类,这种无锁乐观并发控制性能更好。

atomic包.webp

基本原子类

  • 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 替代 数组原子类.webp

引用类型原子类

  • 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
  1. 传统方式使用 synchronized 对转账操作进行线程安全处理粒度太粗,是对象锁
  2. 使用 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();
}
  1. 使用 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:需要频繁读取当前值且对一致性要求较高