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