一、无锁方案
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