在JDK 5之后,Java类库中才开始使用CAS操作,该操作由sun.misc.Unsafe类里面的 compareAndSwapInt()和compareAndSwapLong()等几个方法包装提供。HotSpot虚拟机在内部对这些方法做了特殊处理,即时编译出来的结果就是一条平台相关的处理器CAS指令,没有方法调用的过程, 或者可以认为是无条件内联进去了。不过由于Unsafe类在设计上就不是提供给用户程序调用的类(Unsafe::getUnsafe()的代码中限制了只有启动类加载器(Bootstrap ClassLoader)加载的Class才能访问它),因此在JDK 9之前只有Java类库可以使用CAS,譬如J.U.C包里面的整数原子类,其中的 compareAndSet()和getAndIncrement()等方法都使用了Unsafe类的CAS操作来实现。而如果用户程序也有用CAS操作的需求,那要么就采用反射手段突破Unsafe的访问限制,要么就只能通过Java类库API来间接使用它。直到JDK 9之后,Java类库才在VarHandle类里开放了面向用户程序使用的CAS操作。
Unsafe 实现 CAS
核心 API
- compareAndSwapObject
- compareAndSwapInt
- compareAndSwapInt
原子变量提供的原子性来自CAS操作,CAS来自Unsafe,然后由CPU的 cmpxchg 指令来保证。
Atomic 工具包
Atomic 工具类分为一下几类
- 普通原子类型:提供对boolean、int、long和对象的原子性操作。
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
- 原子类型数组:提供对数组元素的原子性操作。
- AtomicLongArray
- AtomicIntegerArray
- AtomicReferenceArray
- 原子类型字段更新器:提供对指定对象的指定字段进行原子性操作。
- AtomicLongFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicReferenceFieldUpdater
- 带版本号的原子引用类型:以版本戳的方式解决原子类型的ABA问题。
- AtomicStampedReference
- AtomicMarkableReference
- 原子累加器(JDK1.8):AtomicLong和AtomicDouble的升级类型,专门用于数据统计,性能更高。
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
使用案例
多线程累加
下面是创建 1000 个线程分别对 total 进行累加,由于没有对 total 做任何线程安全操作所以我们打印的结果一定是小于或者等于 1000000
public class AtomicTest { public static int total = 0; public static void main(String[] args) throws InterruptedException { CountDownLatch downLatch = new CountDownLatch(1000); for (int i = 0; i < 1000; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { total++; } downLatch.countDown(); }).start(); } downLatch.await(); System.out.println(total); } } // 结果输出 // 999843
通过 AtomicInteger 实现
我们可以通过 integer 的原子类,AtomicInteger 类来进行自增操作,然后调用 getAndIncrement()
方法,保证每次都是原子操作。最终的结果输出:1000000。
public class AtomicTest2 { public static AtomicInteger total = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { CountDownLatch downLatch = new CountDownLatch(1000); for (int i = 0; i < 1000; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { total.getAndIncrement(); } downLatch.countDown(); }).start(); } downLatch.await(); System.out.println(total.get()); } } // 结果输出 // 1000000
再来看看 getAndIncrement
方法它主要是做了哪些操作呢 ?
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } // 然后调用 unsafe 类的本地方法 // unsafe.getAndAddInt 方法 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }