Java并发艺术-CAS
前言
CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术,Doug lea大神在java同步器中大量使用了CAS技术,鬼斧神工的实现了多线程执行的安全性。
CAS的思想很简单:三个参数,一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。
问题
一个n++的问题。
public class Case {
public volatile int n;
public void add() {
n++;
}
}
通过javap -verbose Case看看add方法的字节码指令
public void add();
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field n:I
5: iconst_1
6: iadd
7: putfield #2 // Field n:I
10: return
n++被拆分成了几个指令:
- 执行
getfield拿到原始n; - 执行
iadd进行加1操作; - 执行
putfield写把累加后的值写回n;
通过volatile修饰的变量可以保证线程之间的可见性,但并不能保证这3个指令的原子执行,在多线程并发执行下,无法做到线程安全,得到正确的结果,那么应该如何解决呢?
如何解决
在add方法加上synchronized修饰解决。
public class Case {
public volatile int n;
public synchronized void add() {
n++;
}
}
这个方案当然可行,但是性能上差了点,还有其它方案么?
再来看一段代码
public int a = 1;
public boolean compareAndSwapInt(int b) {
if (a == 1) {
a = b;
return true;
}
return false;
}
如果这段代码在并发下执行,会发生什么?
假设线程1和线程2都过了a==1的检测,都准备执行对a进行赋值,结果就是两个线程同时修改了变量a,显然这种结果是无法符合预期的,无法确定a的最终值。
解决方法也同样暴力,在compareAndSwapInt方法加锁同步,变成一个原子操作,同一时刻只有一个线程才能修改变量a。
除了低性能的加锁方案,我们还可以使用JDK自带的CAS方案,在CAS中,比较和替换是一组原子操作,不会被外部打断,且在性能上更占有优势。
下面以AtomicInteger的实现为例,分析一下CAS是如何实现的。
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public final int get() {return value;}
}
- Unsafe,是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。
- 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
- 变量value用volatile修饰,保证了多线程之间的内存可见性。
看看AtomicInteger如何实现并发下的累加操作:
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//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;
}
假设线程A和线程B同时执行getAndAdd操作(分别跑在不同CPU上):
- AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据Java内存模型,线程A和线程B各自持有一份value的副本,值为3。
- 线程A通过
getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。 - 线程B也通过
getIntVolatile(var1, var2)方法获取到value值3,运气好,线程B没有被挂起,并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为2。 - 这时线程A恢复,执行
compareAndSwapInt方法比较,发现自己手里的值(3)和内存的值(2)不一致,说明该值已经被其它线程提前修改过了,那只能重新来一遍了。 - 重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行
compareAndSwapInt进行比较替换,直到成功。
整个过程中,利用CAS保证了对于value的修改的并发安全,继续深入看看Unsafe类中的compareAndSwapInt方法实现。
public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);
Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
- 先想办法拿到变量value在内存中的地址。
- 通过
Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。
如果是Linux的x86,Atomic::cmpxchg方法的实现如下:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
看到这汇编,内心崩溃
Java并发艺术-CAS的更多相关文章
- JAVA并发编程: CAS和AQS
版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/u010862794/article/details/72892300 说起JAVA并发编程,就不得不聊 ...
- Java并发编程-CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...
- java并发:CAS算法和ABA问题
CAS算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令. CAS用于管理对共享数据的并发访问. java的并发包中,AQS.原子操作类等都是基于CAS实现的. CAS 是一种 ...
- Java 并发(一) --- CAS
CAS 原理 先来看看下面的代码是否可以输出预期的值.开启了两个线程,是否会输出200 呢 结果由于并发的原因,结果会小于或等于200 , 原因出现在 count++; 由于这一行代码存在三个操作: ...
- Java并发/多线程-CAS原理分析
目录 什么是CAS 并发安全问题 举一个典型的例子i++ 如何解决? 底层原理 CAS需要注意的问题 使用限制 ABA 问题 概念 解决方案 高竞争下的开销问题 什么是CAS CAS 即 compar ...
- Java 并发学习总结
目录 基础篇 进阶篇 并发编程的的三个概念(特性)? JMM(Java 内存模型) volatile 关键字 1. Java 内存模型(为什么要有 volatile) 2. volatile 原理 追 ...
- 读《Java并发编程的艺术》(一)
离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督 ...
- Java并发编程的艺术读书笔记(1)-并发编程的挑战
title: Java并发编程的艺术读书笔记(1)-并发编程的挑战 date: 2017-05-03 23:28:45 tags: ['多线程','并发'] categories: 读书笔记 --- ...
- java并发编程的艺术(一)---锁的基本属性
本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...
随机推荐
- ambassador 学习七 Mapping说明
mapping 通过rest 资源与k8s 的service进行关联,ambassador 必须有一个或者多个提供访问servide 的mapping定义 mapping 可以包含的配置 rewrit ...
- Ant build.xml相关属性详解
关键字: ant build.xml Ant的概念 可能有些读者并不连接什么是Ant以及入可使用它,但只要使用通过Linux系统得读者,应该知道make这个命令.当编译Linux内核及一些软件的源程序 ...
- 一个filebeat实例 设置多topic设置
方法1:一实例多topic: https://discuss.elastic.co/t/filebeat-5-0-output-to-kafka-multiple-topics/67934 The d ...
- django创建第一个项目helloworld
环境:centos 7,已安装python 3.6环境 1.安装django并创建django第一个项目 1.1.使用pip安装django# pip install Django或指定安装版本# p ...
- Window平台下React Native 开发环境搭建
1. 安装Node.js 2. 安装react-native-cli 命令行工具 npm install -g react-nativew-cli 3. 创建项目 $ react-native ini ...
- [C++ Primer] 第6章: 函数
参数传递 const形参和实参: 顶层const作用于对象本身, 和其他初始化过程一样, 当用实参初始化形参时会忽略掉顶层const, 换句话说, 形参顶层const被忽略掉了, 当形参有顶层cons ...
- vagrant 相关记录
最近安装vagrant 出错的最大的可能性是BOX 路径不太对, 好像和目录的大小写有关系,请检查 $ vagrant init # 初始化$ vagrant up # 启动虚拟机$ vagrant ...
- 【转】java中创建对象的方法
有4种显式地创建对象的方式: 1.用); System.out.println("call default constructor"); } public Customer(Str ...
- android.support.v7.internal.widget.ActionBarOverlayLayout Couldn't Be Initialized
问题症状: Android Studio 1.2 (Build 141.1890965) 新建工程,自动build完成后,Layout Editor无法预览Layout文件,报错内容: Renderi ...
- phpcms 实现动态价格
什么是动态价格(自命名)?—— 一般来说商品有个固定的价格,随着节假日或者促销活动可能会发生价格变化,结束后又恢复原价,如果每次价格变化都需要修改价格,那么不仅在时间上不好把握,也需要消耗更多的人力 ...