深入理解Java中的CAS(一)

深入理解Java中的CAS(一)

大家好,我是栗子为

又有好长时间没有和大家见面了,最近小为也是忙着入职,所以在学习上有了一点点懈怠,培训的日子每天的安排真是满满当当,小为也是心有余而力不足。这不,一有了空闲时间,小为决定接着上次打开的话题继续聊下去,Java并发编程中的那些事,这对我即将要处理的工作也有一定的帮助

小为今天想和大家说的是面试中常会被问到的CAS原理,我想这个知识点大家肯定不陌生,甚至还能说出它的全称Compare And Swap。这是一点没错,希望大家跟着小为学完后,不仅仅是讲出它的全称,更是让大家在面试中轻松地讲清楚它的原理

我们都知道可以利用原子类来处理一些多线程的场景,保证数据的一致性,原子类就是利用到了CAS这样的机制,那我们思考一个问题,在CAS之前,我们是如何不利用原子类来保证线程安全的呢?



01


不使用原子类保证线程安全


public class WithoutAtomic {
  volatile int number = 0;

  public int getNumber() {
    return number;
  }

  public synchronized void setNumber() {
    number++;
  }
}

可以通过对写操作加锁来保证线程安全,对于读操作不用加锁,volatile关键字能保证每次都从主内存中读取最新的值

使用原子类保证线程安全

public class WithAtomic {
  AtomicInteger atomicInteger = new AtomicInteger();

  public int getAtomicInteger() {
    return atomicInteger.get();
  }

  public void setAtomicInteger() {
    atomicInteger.getAndIncrement();
  }
}

这是不是有点像乐观锁的思想,没错

接着就引入我们今天要聊的内容-CAS



02


什么是CAS?

Compare and swap,翻译过来是比较并交换

它包含有三个操作数:

  • 内存位置V
  • 预期原值A
  • 更新值B

我们在执行CAS操作的时候,会先将内存位置的值与预期原值进行比较:

  • 如果匹配,那处理器会自动将该位置值更新为新值
  • 如果不匹配,处理器不做任何操作或者重试(自旋),多个线程同时执行CAS操作只有一个会成功

我们来看看硬件层面是怎么实现CAS的?

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg

执行cmpxchg指令的时候,首先会判断系统是否为多核系统,如果是,就会给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就是说,CAS原子性实际上是CPU实现独占,相比synchronized重量级锁,排他时间要短很多,所以在多线程情况下性能会比较好

看一个常见的🌰

public static void main(String[] args) {
  AtomicInteger atomicInteger = new AtomicInteger(5);
  System.out.println(atomicInteger.compareAndSet(52022) + "t" + atomicInteger.get());
}

compareAndSet方法的底层逻辑如下

public final boolean compareAndSet(int expect, int update) {
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

其实是调用了unsafecompareAndSwapInt方法,unsafe类有如下native方法

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:表示需要修改数据的期望的值
  • var5/var6:表示需要修改为的新值



03


Unsafe类


在上面我们提到了Unsafe类,CAS是通过实现Unsafe类的compareAndSwapInt方法来实现的,那么Unsafe类到底是什么呢?

Unsafe类是CAS的核心类,由于Java不能直接访问底层系统,需要通过本地native方法来访问,基于该类可以操作特定内存的数据。

注:Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务



04


AtomicInteger实现原理


1.通过Unsafe类的静态方法,得到unsafe实例

private static final Unsafe unsafe = Unsafe.getUnsafe();

2.Unsafe根据内存偏移地址获取数据

static {
  try {
    valueOffset = unsafe.objectFieldOffset
      (AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) { throw new Error(ex); }
}

变量valueOffset表示该变量值在内存中的偏移地址

3.volatile保证多线程之间内存可见性

private volatile int value;



05


AtomicInteger之getAndIncrement


我们再来看文章最开始举的🌰,对于i++这种线程不安全的方式,atomicInteger又是怎么保证的呢?

这就要用到getAndIncrement这个方法

看看底层源码

public final int getAndIncrement() {
  return unsafe.getAndAddInt(this, valueOffset, 1);
}

同样调用了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;
}

var5表示当前对象在var2偏移地址上的最新的实际值

do…while循环满足条件:

对象var1在偏移地址var2上的值如果和我们期望的var5值不同,则需要循环(自旋操作)重新判断

如果相同,那么我们更新var5的值为var5+var4,即做了一次自增操作



06


总结


CAS即compare and swap,先比较再交换,是一种原子性操作,底层是利用了Unsafe类,依赖于硬件的功能,实现原子操作,CAS是一种系统原语,属于操作系统用语范畴,是由若干条指令组成的,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成数据不一致问题。

关于CAS还有一些内容,例如原子引用、自旋锁以及ABA问题,这些小为就下次再和大家分享啦~

感谢大家坚持到了最后,希望大家都能早日拿到心仪的offer~

关注六只栗子,面试不迷路,我们下次再见~

作者    栗子为

编辑   一口栗子  




深入理解Java中的CAS(一)

深入理解Java中的CAS(一)

深入理解Java中的CAS(一)

深入理解Java中的CAS(一)


原文始发于微信公众号(六只栗子):深入理解Java中的CAS(一)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88471.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!