Cas
CAS,compareand swap的缩写,中文翻译成比较并交换。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
public class Test {
public static int count = 0;
private final static int MAX_TREAD=10;
public static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
/*CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。
使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。
当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。*/
CountDownLatch latch = new CountDownLatch(MAX_TREAD);
//匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
count++;
atomicInteger.getAndIncrement();
}
latch.countDown(); // 当前线程调用此方法,则计数减一
}
};
//同时启动多个线程
for (int i = 0; i < MAX_TREAD; i++) {
new Thread(runnable).start();
}
latch.await(); // 阻塞当前线程,直到计数器的值为0
System.out.println("理论结果:" + 1000 * MAX_TREAD);
System.out.println("static count: " + count);
System.out.println("AtomicInteger: " + atomicInteger.intValue());
}
}
输出:
理论结果:10000
static count: 9113
AtomicInteger: 10000
AtomicInteger源码解析:
通过源码我们发现AtomicInteger的增减操作都调用了Unsafe实例的方法,下面我们对Unsafe类做介绍:
Unsafe类
Unsafe 是位于 sun.misc 包下的一个类,Unsafe 提供了CAS 方法,直接通过native 方式(封装 C++代码)调用了底层的 CPU 指令 cmpxchg。
Unsafe类,翻译为中文:危险的,Unsafe全限定名是 sun.misc.Unsafe,从名字中我们可以看出来这个类对普通程序员来说是“危险”的,一般应用开发者不会用到这个类。
CAS的缺点
1.ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
JDK 提供了两个类 AtomicStampedReference、AtomicMarkableReference 来解决 ABA 问题。
2.只能保证一个共享变量的原子操作。一个比较简单的规避方法为:把多个共享变量合并成一个共享变量来操作。 JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个 AtomicReference 实例后再进行 CAS 操作。比如有两个共享变量 i=1、j=2,可以将二者合并成一个对象,然后用 CAS 来操作该合并对象的 AtomicReference 引用。
3.循环时间长开销大。高并发下N多线程同时去操作一个变量,会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。
解决 CAS 恶性空自旋的较为常见的方案为:
分散操作热点,使用 LongAdder 替代基础原子类 AtomicLong。
使用队列削峰,将发生 CAS 争用的线程加入一个队列中排队,降低 CAS 争用的激烈程度。JUC 中非常重要的基础类 AQS(抽象队列同步器)就是这么做的。
1.以空间换时间,LongAdder。
解决 ABA 问题:
1.使用 AtomicStampedReference 解决 ABA 问题。