一文读懂 Java 中的原子类

简介: Java 并发包中的原子类都是基于无锁方案实现的,相较于传统的互斥锁,无锁并没有加锁、解锁、线程切换的消耗,因此无锁解决方案的性能更好,同时无锁还能够保证线程安全。

一、无锁方案

Java 并发包中的原子类都是基于无锁方案实现的,相较于传统的互斥锁,无锁并没有加锁、解锁、线程切换的消耗,因此无锁解决方案的性能更好,同时无锁还能够保证线程安全。


1. 无锁方案的实现原理

无锁主要依赖 CAS(Compare And Swap) ,即比较并交换,CAS 是一条 CPU 指令,其本身是能够保证原子性的。CAS 中有三个参数:

  • 共享变量的内存地址 A
  • 用于比较的值 B
  • 共享变量的新值 C
public class SimpleCAS {
    private int value;
    public synchronized int cas(int expectVal, int newVal){
        int curVal = value;
        if (expectVal == curVal){
            value = newVal;
        }
        return curVal;
    }
}

上面的代码展示了 CAS 的简单实现,从内存中读出当前 value 的值,并且需要判断,期望值 expectVal == curVal 的时候,才会将 value 更新为新值。

仍然以上面的代码,来实现一个简单的,基于 CAS 的线程安全的 value+1 方法。这里的 cas 方法仅用于帮助理解,所以执行结果可能有出入。

public class SimpleCAS {
    private volatile int value;
    public void addValue(){
        int newVal = value + 1;
        while (value != cas(value, newVal)){
            newVal = value + 1;
        }
    }
    private synchronized int cas(int expectVal, int newVal){
        int curVal = value;
        if (expectVal == curVal){
            value = newVal;
        }
        return curVal;
    }
}

线程首先读取 value 的值并加 1,如果此时有另一个线程更新了 value,则期望值和 value 不相等,更新失败。更新失败后,循环尝试,重新读取 value 的值,直到更新成功退出循环。


2. ABA 问题

无锁的实现方案中需要注意的一个问题便是 ABA 问题。

例如上面的代码,value 的初始值为 0,线程 t1 取到了 value 的值,并将其更新为 1,然后线程又将 value 更新为 0。

假如这个过程中有另外一个线程 t2,和 t1 同时取初始值为 0 的 value,t2 在 t1 执行完后更新 value,这个时候 value 虽然还是为 0,但已经被 t1 修改过了。

大多数情况下,我们并不需要关心 ABA 问题,例如数值型数据的加减,但是对象类型的数据遇到了 ABA 问题的话,可能前后的属性已经发生了变化,所以需要解决。

解决的办法也很简单,给对象类型的数据加上一个版本号即可,每更新一次,版本号加 1,这样即使对象数据从 A 变成 B 后 又变成 A,但是版本号是递增的,就可以分辨出对象还是被修改过的。


二、原子类

1. 原子化基本数据类型

有三个实现类:AtomicBoolean、AtomicInteger、AtomicLong

常用的方法如下,以 AtomicInteger 为例,其他的类似:

AtomicInteger i = new AtomicInteger(0);
i.getAndSet(int newValue);//获取当前值并设置新值
i.getAndIncrement();//相当于 i ++
i.incrementAndGet();//相当于 ++ i
i.getAndDecrement();//相当于 i --
i.decrementAndGet();//相当于 -- i
i.addAndGet(int delta);//相当于 i + delta,并返回添加后的值
i.getAndAdd(int delta);//相当于 i + delta,并返回添加前的值
i.compareAndSet(int expect, int update);//CAS 操作,返回 boolean值,表示是否更新成功
i.getAndUpdate(update -> 10);//通过函数更新值
i.updateAndGet(update -> 10);//类似上面


2. 原子化对象引用类型

实现类分别是:AtomicReference、AtomicStampedReference、AtomicMarkableReference,其中后两个可以实现了解决 ABA 问题的方案。

AtomicReference 常用的方法如下:

//假设有一个叫做 Order 的类
AtomicReference<Order> orderReference = new AtomicReference<>();
orderReference.getAndSet(Order newValue);//获取并设置
orderReference.set(Order order);//设置值
Order order1 = orderReference.get();//获取对象
orderReference.compareAndSet(Order expect, Order update);//比较交换
orderReference.getAndUpdate();//通过函数更新值
orderReference.updateAndGet();
AtomicStampedReference 需要传入初始值和初始 stamp,其中 stamp 相当于对象的版本号(用来解决 ABA 问题),使用示例如下:
AtomicStampedReference<String> reference = new AtomicStampedReference<>("roseduan", 0);
//尝试修改stamp的值
boolean b = reference.attemptStamp(reference.getReference(), 10);
//获取值
String str = reference.getReference();
//获取stamp
int stamp = reference.getStamp();
//重新设置值和stamp
reference.set("I am not roseduan", 20);
//比较交换
boolean b1 = reference.compareAndSet("roseduan", "jack", 20, 0);

AtomicMarkableReference 使用一个 mark 标记(boolean 类型) 代替了 AtomicStampedReference 中的 stamp,用这种更简单的方式来解决 ABA 问题。使用的方式和上面的类似,只是将方法中的 stamp 变为 boolean 类型的值即可。


3. 原子化数组类型

实现类有三个:

  • AtomicIntegerArray:原子化的整型数组
  • AtomicLongArray:原子化长整型数组
  • AtomicReferenceArray:原子化对象引用数组

使用和原子化基本类型都是差不多的,只是需要在方法中加上数组下标即可。


4. 原子化对象属性更新器

也有三个实现类:

  • AtomicIntegerFieldUpdater:更新对象的整型属性
  • AtomicLongFieldUpdater:更新对象的长整型属性
  • AtomicReferenceFieldUpdater:更新对象的引用型属性

这三个类都是利用 Java 的反射机制实现的,并且为了保证原子性,要求被更新的对象的属性必须是 volatile 类型的。使用示例如下:

@Data
@Builder
public class User {
    private volatile int age;
    private volatile long number;
    private volatile String name;
    public static void main(String[] args) {
        User user = User.builder().age(22).number(15553663L).name("roseduan").build();
        //更新age属性的值
        AtomicIntegerFieldUpdater<User> integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
        integerFieldUpdater.set(user, 25);
        //更新number属性的值
        AtomicLongFieldUpdater<User> longFieldUpdater = AtomicLongFieldUpdater.newUpdater(User.class, "number");
        longFieldUpdater.set(user, 1000101L);
        //更新对象类型的属性的值
        AtomicReferenceFieldUpdater<User, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
        referenceFieldUpdater.set(user, "I am not roseduan");
        System.out.println(user.toString());
    }
}

程序中创建了一个 User 类,有三个属性 age、number、name 分别对应整型、长整型、引用类型。然后使用对象属性更新器进行属性值的更新,更新器的其他方法的使用和前面说到的几种原子化类型类似。


5. 原子化累加器

实现类有四个:

  • DoubleAdder
  • DoubleAccumulator
  • LongAdder
  • LongAccumulator

这几个类的功能有限,仅用来执行累加操作,但是速度非常快。下面介绍 DoubleAdder 和 DoubleAccumulator 的用法,其余两个类似。

//DoubleAccumulator使用示例
DoubleAccumulator a = new DoubleAccumulator(Double::sum, 0);//设初始值为0
//累加
a.accumulate(1);
a.accumulate(2);
a.accumulate(3);
a.accumulate(4);
System.out.println(a.get());//输出10
//DoubleAdder使用示例
DoubleAdder adder = new DoubleAdder();
adder.add(1);
adder.add(2);
adder.add(3);
adder.add(4);
adder.add(5);
System.out.println(adder.intValue());//输出15



相关文章
|
7月前
|
存储 缓存 算法
Java并发基础:原子类之AtomicMarkableReference全面解析
AtomicMarkableReference类能够确保引用和布尔标记的原子性更新,有效避免了多线程环境下的竞态条件,其提供的方法可以轻松地实现基于条件的原子性操作,提高了程序的并发安全性和可靠性。
Java并发基础:原子类之AtomicMarkableReference全面解析
|
7月前
|
安全 Java
Java一分钟之-并发编程:原子类(AtomicInteger, AtomicReference)
【5月更文挑战第18天】Java并发编程中的原子类如`AtomicInteger`和`AtomicReference`提供无锁原子操作,适用于高性能并发场景。`AtomicInteger`支持原子整数操作,而`AtomicReference`允许原子更新对象引用。常见问题包括误解原子性、过度依赖原子类以及忽略对象内部状态的并发控制。要避免这些问题,需明确原子操作边界,合理选择同步策略,并精确控制原子更新。示例代码展示了如何使用这两个类。正确理解和使用原子类是构建高效并发程序的关键。
85 1
|
7月前
|
缓存 安全 算法
Java并发基础:原子类之AtomicInteger全面解析
【2月更文挑战第2天】AtomicInteger类提供了线程安全的整数操作,它通过利用底层硬件的原子性指令,能够在多线程环境中高效地实现整数的无锁更新,避免了传统同步机制带来的性能开销,在高并发场景下成为计数器可大幅提高程序的执行效率,同时又保证了数据一致性。
232 16
Java并发基础:原子类之AtomicInteger全面解析
|
7月前
|
存储 算法 安全
Java并发基础:原子类之AtomicBoolean全面解析
【2月更文挑战第1天】 AtomicBoolean类优点在于能够确保布尔值在多线程环境下的原子性操作,避免了繁琐的同步措施,它提供了高效的非阻塞算法实现,可以大大提成程序的并发性能,AtomicBoolean的API设计非常简单易用。
241 3
Java并发基础:原子类之AtomicBoolean全面解析
|
7月前
|
安全 Java
Java中的基本类型原子类介绍
Java中的基本类型原子类介绍
57 1
|
7月前
|
Java
【面试问题】用过Java 中哪些原子类?
【1月更文挑战第27天】【面试问题】用过Java 中哪些原子类?
|
算法 Java
第二季:3原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?【Java面试题】
第二季:3原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?【Java面试题】
107 0
|
Java 开发工具
Java的Atomic原子类
Java SDK 并发包里提供了丰富的原子类,我们可以将其分为五个类别,这五个类别提供的方法基本上是相似的,并且每个类别都有若干原子类。
Java的Atomic原子类
|
算法 安全 Java
【Java并发编程 八】JUC并发包下原子类
【Java并发编程 八】JUC并发包下原子类
86 0
Java:从单线程计数器到多线程数据同步synchronized和原子类Atomic
Java:从单线程计数器到多线程数据同步synchronized和原子类Atomic
170 0