JUC-18-CAS

JUC-18-CAS

1. 简介

  • CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。CAS也是现在面试经常问的问题,本文将深入的介绍CAS的原理。

[参考博客][https://blog.csdn.net/v123411739/article/details/79561458?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.edu_weight]

2. CAS

2.1 一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.CountDownLatch;

public class VolatileTest {

public static volatile int race = 0;

private static final int THREADS_COUNT = 20;

private static CountDownLatch countDownLatch = new CountDownLatch(THREADS_COUNT);

public static void increase() {
race++;
}

public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
countDownLatch.countDown();
}
});
threads[i].start();
}
countDownLatch.await();
System.out.println(race);
}
}

我们知道,运行完这段代码之后,并不会获得期望的结果,而且会发现每次运行程序,输出的结果都不一样,都是一个小于200000的数字。

通过分析字节码我们知道,这是因为volatile只能保证可见性,无法保证原子性,而自增操作并不是一个原子操作(如下图所示),在并发的情况下,putstatic指令可能把较小的race值同步回主内存之中,导致我们每次都无法获得想要的结果。那么,应该怎么解决这个问题了?

mark

解决方案1:Synchronized

  • 首先我们想到的是用synchronized来修饰increase方法。

mark

使用synchronized修饰后,increase方法变成了一个原子操作,因此是肯定能得到正确的结果。但是,我们知道,每次自增都进行加锁,性能可能会稍微差了点,有更好的方案吗?

解决方案2:Atomic

  • 答案当然是有的,这个时候我们可以使用Java并发包原子操作类(Atomic开头),例如以下代码。

  • 将例子中的代码稍做修改:race改成使用AtomicInteger定义,“race++”改成使用“race.getAndIncrement()”,AtomicInteger.getAndIncrement()是原子操作,因此我们可以确保每次都可以获得正确的结果,并且在性能上有不错的提升

  • 通过方法调用,我们可以发现,getAndIncrement方法调用getAndAddInt方法,最后调用的是compareAndSwapInt方法,即本文的主角CAS,接下来我们开始介绍CAS。

mark

mark

2.2 JDK中的CAS实现

  1. JDK中提供了CAS支持?
  • 通过调用JNI(java Native interface)

  • java中提供了对CAS的支持,具体在sun.misc.unsafe类中

1
2
3
4
5
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

参数var1 : 要操作的对象

参数var2 : 要操作的对象属性地址的偏移量

参数var4 : 期望值

参数var6 : 需要更新的新值

  • 并且compareAndSwapLong 是使用CPU底层指令来实现的。以常用的Intel x86平台,最后映射到cpu的 cmpxchg 来实现,这是一个原子指令。
  • 系统底层进行CAS操作的时候,会判断当前系统是否是多核心,如果是就给 “ 总线加锁”,加锁之后,只有一个线程会对总线加锁成功,也就是说CAS的操作是平台级别的。

2.3 CAS带来的问题

  • 循环时间长开销很大。

  • 只能保证一个变量的原子操作。

  • ABA 问题 : 一句话总结,狸猫换太子

    • 解决方案: AtomicStampReference (Pair对象)

    • // 内部类:不但要提供数据引用,还要提供版本号    
      private static class Pair<T> {
              final T reference;
              final int stamp;
              private Pair(T reference, int stamp) {
                  this.reference = reference; 
                  this.stamp = stamp;
              }
              static <T> Pair<T> of(T reference, int stamp) {
                  return new Pair<T>(reference, stamp);
              }
          }
      
          private volatile Pair<V> pair
      <!--2-->
      
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信