一、原子类
原子类包装了一个变量,然后提供对这个变量的原子操作的方法。原子类中对变量的操作,都是原子操作。
原子类用来把变量的操作封装成原子操作,也就是保证了原子性。
当你的代码保证了有序性和可见性时,可以使用原子类来保证原子性,从而避免synchronized带来的高性能开销。
二、分类
1)基本原子类
- AtomicBoolean:布尔型
- AtomicInteger:整型
- AtomicLong:长整形
3)数组原子类
- AtomicIntegerArray:整型数组
- AtomicLongArray:长整型数组
- AtomicReferenceArray:引用型数组
3)引用原子类
- AtomicReference:原子更新引用类型。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)
4)字段原子类
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
三、实现原理
CAS 就是 Compare and Swap (比较并操作) 的意思。很多的 CPU 直接支持 CAS 指令。
CAS 是一项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。
JDK1.5 中引入了底层的支持,在 int、long 和对象的引用等类型上都公开了 CAS 的操作,并且 JVM 把它们编译为底层硬件提供的最有效的方法,在运行 CAS 的平台上,运行时把它们编译为相应的机器指令。在 java.util.concurrent.atomic 包下面的所有的原子变量类型中,比如 AtomicInteger,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的 CAS 操作。
在 CAS 操作中,会出现ABA 问题
。CAS算法实现一个重要前提:需要取出内存中某时刻的数据,而在下一时刻比较并替换。但是在这个时间差内会导致数据的变化。比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
有个简单的方案解决ABA问题:不要单纯的更新某个引用的值,例如更新两个值,包括一个引用和一个版本号,即使这个值由 A 变为 B,然后 B 变为 A,版本号也是不同的,由此可以判断这个值是改变了的。
AtomicStampedReference 和 AtomicMarkableReference 支持在两个变量上执行原子的条件更新。AtomicStampedReference 更新一个 “对象-引用” 二元组,通过在引用上加上 “版本号”,从而避免 ABA 问题,AtomicMarkableReference 将更新一个“对象引用-布尔值”的二元组。
四、使用
4.1 基本原子类,以AtomicInteger为例
public class Test {
private AtomicInteger atomicInteger = new AtomicInteger(0);
private CountDownLatch countDownLatch = new CountDownLatch(1000);
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
service.execute(new Runnable() {
@Override
public void run() {
test.increment();
test.countDownLatch.countDown();
}
});
}
// 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
// 主线程开始打印,这时主线程取到的并不是最终结果
test.countDownLatch.await();
System.out.println("atomicInteger: " + test.atomicInteger.get());
service.shutdownNow();
}
public void increment() {
atomicInteger.incrementAndGet();
}
}
4.2 数组原子类:以 AtomicIntegerArray为例
public class Test {
private int [] ints = {0, 0, 0};
private AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(ints);
private CountDownLatch countDownLatch = new CountDownLatch(1000);
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
ExecutorService service = Executors.newCachedThreadPool();
// 把数组中的每个字加1000,每个线程加1次
for (int i = 0; i < 1000; i++) {
service.execute(new Runnable() {
@Override
public void run() {
test.increment();
test.countDownLatch.countDown();
}
});
}
// 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
// 主线程开始打印,这时主线程取到的并不是最终结果
test.countDownLatch.await();
System.out.println("atomicIntegerArray: " + test.atomicIntegerArray);
service.shutdownNow();
}
public void increment() {
for (int i = atomicIntegerArray.length() - 1; i >= 0; i--) {
atomicIntegerArray.incrementAndGet(i);
}
}
}
4.3 引用原子类:以 AtomicReference 为例
public class Test {
private int num = 0;
private int count = 0;
private AtomicReference<Integer> atomicReference = new AtomicReference<> (num);
private CountDownLatch countDownLatch = new CountDownLatch(1000);
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
service.execute(new Runnable() {
@Override
public void run() {
test.increment();
test.countDownLatch.countDown();
}
});
}
// 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
// 主线程开始打印,这时主线程取到的并不是最终结果
test.countDownLatch.await();
System.out.println("atomicReference: " + test.atomicReference.get());
service.shutdownNow();
}
public void increment() {
synchronized (atomicReference) {
count++;
}
atomicReference.getAndSet(count);
}
}
4.4 字段原子类:以 AtomicIntegerFieldUpdater 为例
注意:
- 因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法 newUpdater() 创建一个更新器,并且需要设置想要更新的类和属性。
- 更新类的字段(属性)必须使用 public volatile 修饰符。
public class Test {
private User user = new User("T_0", 0);
private AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
private CountDownLatch countDownLatch = new CountDownLatch(1000);
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
service.execute(new Runnable() {
@Override
public void run() {
test.increment(1);
test.countDownLatch.countDown();
}
});
}
// 等待所有的线程都执行完成才打印,不然会导致计算线程还没执行完成,
// 主线程开始打印,这时主线程取到的并不是最终结果
test.countDownLatch.await();
System.out.println("atomicReference: " + test.atomicIntegerFieldUpdater.get(test.user));
service.shutdownNow();
}
public void increment(int o) {
atomicIntegerFieldUpdater.getAndAdd(user, o);
}
}
class User {
private String name;
public volatile int old; // 必须为public volatile
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getOld() {
return old;
}
public void setOld(int old) {
this.old = old;
}
}
五、AtomicStampedReference 与 AtomicMarkableReference
原子类通过volatile和Unsafe提供的CAS函数实现原子操作。 自旋+CAS的无锁操作保证共享变量的线程安全。但是 CAS操作可能出现ABA问题,AtomicStampedReference 与 AtomicMarkableReference正是为了解决ABA问题而诞生的。
举个通俗点的例子,你倒了一杯水放桌子上,干了点别的事,然后同事把你水喝了又给你重新倒了一杯水,你回来看水还在,拿起来就喝,如果你不管水中间被人喝过,只关心水还在,这就是ABA问题。如果你是一个讲卫生讲文明的小伙子,不但关心水在不在,还要在你离开的时候水被人动过没有,因为你是程序员,所以就想起了放了张纸在旁边,写上初始值0,别人喝水前麻烦先做个累加才能喝水。
AtomicStampedReference
的构造方法中initialStamp用来唯一标识引用变量,在构造器内部,实例化了一个Pair对象,Pair对象记录了对象引用和时间戳信息,采用int作为唯一标识,实际使用的时候,要保证标识唯一(一般做成自增的),如果标识重复,还示会出现ABA的问题,所以在使用时一定要保证标识不重复。
AtomicMarkableReference
AtomicStampedReference可以知道,引用变量中途被更改了几次。有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference。
AtomicMarkableReference的唯一区别就是不再用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。
public class AtomicStampedReferenceTest {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(0, 0);
public static void main(String[] args) throws InterruptedException {
final int stamp = atomicStampedReference.getStamp();
final Integer reference = atomicStampedReference.getReference();
System.out.println(reference+"============"+stamp);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(reference + "-" + stamp + "-"
+ atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1));
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Integer reference = atomicStampedReference.getReference();
System.out.println(reference + "-" + stamp + "-"
+ atomicStampedReference.compareAndSet(reference, reference + 10, stamp, stamp + 1));
}
});
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println(atomicStampedReference.getReference());
System.out.println(atomicStampedReference.getStamp());
}
}