前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解CAS

深入理解CAS

作者头像
胖虎
发布2019-06-26 16:56:08
5890
发布2019-06-26 16:56:08
举报
文章被收录于专栏:晏霖晏霖

前言

首先这篇文章是对前文深入理解ConcurrentHashMap中提到的CAS概念做补充的。其次是讲解CAS理论,我也看过很多关于CAS的博客,重复性,概念性都太强了,我要做的与众不同,我会把我所理解的用通俗易懂的语言描述出来的。

正文

什么是CAS

CAS(比较与交换,Compare and swap)是一种有名的无锁算法。

CAS工作原理

CAS指令需要有3个操作数,分别是内存为止(在Java中可以简单理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则他就不执行更新,但是无论是否更新了V的值,都会返回V的旧值,上述的处理过程是一个原子操作。

如何使用CAS操作来避免阻塞同步

下面的代码主要是使用了20个线程进行自增10000次来证明原子性.运行结果是:20000

public static AtomicInteger race = new AtomicInteger(0);

private static final int THREADS_COUNT = 20;

public static void increase() {

race.incrementAndGet();

}

@Test

public void atomicTest() {

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();

}

}

});

threads[i].start();

}

while (Thread.activeCount() > 1)

Thread.yield();

System.out.println(race);

}

我们使用了AtomicInteger了,程序输出正确结果,一切都要归功于incrementAndGet()方法的原子性,该方法无限循环,不断尝试将一个一个比当前值大1的新值赋给自己,如果失败了那说明在执行“获取-设置“操作的时候值已经有了修改,于是再次循环进行下一次操作,只带设置成功为止,它的原理实现其实非常简单。代码如下:

/**

* Atomically increments by one the current value.

*

* @return the updated value

*/

public final int incrementAndGet() {

return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}

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;

}

上面的核心代码都在Unsafe.class 大家可以自己进去看一看。

CAS缺点

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说它值没有被其他线程改变过吗?

如果在这段期间它的值曾经改成了B,后来又改成了A,那么CAS操作就会误认为它没有改变过,这个漏洞称为“ABA”问题。J.U.C包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性,如果需要解决ABA问题,改用传统的互斥同步(典型的就是synchronized 和Lock)可能会比原子类更高效。

总结:Unsafe类是CAS实现的核心。 从名字可知,这个类标记为不安全的,CAS会使得程序设计比较负责,但是由于其优越的性能优势,以及天生免疫死锁(根本就没有锁,当然就不会有线程一直阻塞了),更为重要的是,使用无锁的方式没有所竞争带来的开销,也没有线程间频繁调度带来的开销,他比基于锁的方式有更优越的性能,所以在目前被广泛应用,我们在程序设计时也可以适当的使用.不过由于CAS编码确实稍微复杂,而且jdk作者本身也不希望你直接使用unsafe,所以如果不能深刻理解CAS以及unsafe还是要慎用,使用一些别人已经实现好的无锁类或者框架就好了。

附:

JVM中的CAS

堆中对象的分配

简单的说new出来一个对象之前大小其实已经固定,把他放到堆里以什么形式储存的呢?

由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:

CAS

实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。

TLAB

如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。

虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。

注:对本文有异议或不明白的地方微信探讨

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 晏霖 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 正文
    • 什么是CAS
      • CAS工作原理
        • 如何使用CAS操作来避免阻塞同步
        • CAS缺点
          • JVM中的CAS
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档