并发学习-【转】深入理解并发之CompareAndSet(CAS)
2016-04-11 18:24
519 查看
程老师原文地址:http://flychao88.iteye.com/blog/2269438原文如下:一、CAS简介CAS:Compare and Swap, 翻译成比较并交换。 java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁,使用这些类在多核CPU的机器上会有比较好的性能. CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 今天我们主要是针对AtomicInteger的incrementAndGet做深入分析。 二、JAVA实现部分 Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
循环的内容是
1.取得当前值
2.计算+1后的值
3.如果当前值没有被覆盖的话设置那个+1后的值
4.如果设置没成功, 再从1开始 在这个方法中可以看到compareAndSet这个方法,我们进入看一下。Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
调用UnSafe这个类的compareAndSwapInt Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
JAVA程序也就跟踪到这里为止了,剩下的就是通过JNI调用C程序了,可是我奇怪的是为什么变量名都是var1,var2这样的命名呢?JAVA编程规范不是说不使用1,2等没有含义的字符命名吗? 三、JNI原生实现部分在openJDK中找到找到unsafe.cpp这个文件,代码如下:Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
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
核心方法是compxchg,这个方法所属的类文件是在OS_CPU目录下面,由此可以看出这个类是和CPU操作有关,进入代码如下:Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
这个方法里面都是汇编指命,看到LOCK_IF_MP也有锁指令实现的原子操作,其实CAS也算是有锁操作,只不过是由CPU来触发,比synchronized性能好的多。
**********************学习笔记开始*********************************又开始深入学习了,大神就是这么厉害。写个demo测试下吧。跟大神一样就演示下AtomicInteger
![](http://img1.ph.126.net/33hgACn7ZbNFEljDAXBrTQ==/6598100009112245878.png)
可以看出并发情况下,不采用线程安全的类容易出错。当然这背后是基于cas的,上文已经介绍了。 知识点1多核cpu如何去实现“原子操作”。相关知识点:缓存行(cacheline)、CPU流水线(CPU line) 处理器保证系统从内存当中读取一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器是不能访问这个字节的地址的。最新的Intel X86能保证单处理器对同一缓存行里进行的16/32/64位操作是原子的。复杂的内存操作如跨总线宽度、跨缓存行,处理器通过总线锁定和缓存锁定来保证原子性。这两种机制Intel提供很多lock指令来实现,比如上文说的cmpxchg。 知识点2:JDK文档说cas同时具有volatile读和volatile写的内存语义。 针对上文说的cmpxchg指令,在多处理器下会加入LOCK前缀(LOCK cmpxchg),单处理器会忽略LOCK前缀。Intel对lock前缀有特殊说明: 1.根据内存区域不同提供总线锁定和缓存锁定。 2.禁止改指令与之前和之后的读指令和写指令重排序。3.把写缓冲区的数据全部刷新到内存中。2,、3点所具有的内存屏障效果,满足了volatile读和volatile写的内存语义。知识点3:CAS缺点:问题1:ABA问题因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
参考:http://ifeve.com/atomic-operation/
![](http://flychao88.iteye.com/images/icon_star.png)
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
循环的内容是
1.取得当前值
2.计算+1后的值
3.如果当前值没有被覆盖的话设置那个+1后的值
4.如果设置没成功, 再从1开始 在这个方法中可以看到compareAndSet这个方法,我们进入看一下。Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
调用UnSafe这个类的compareAndSwapInt Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
JAVA程序也就跟踪到这里为止了,剩下的就是通过JNI调用C程序了,可是我奇怪的是为什么变量名都是var1,var2这样的命名呢?JAVA编程规范不是说不使用1,2等没有含义的字符命名吗? 三、JNI原生实现部分在openJDK中找到找到unsafe.cpp这个文件,代码如下:Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
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
核心方法是compxchg,这个方法所属的类文件是在OS_CPU目录下面,由此可以看出这个类是和CPU操作有关,进入代码如下:Java代码
![](http://flychao88.iteye.com/images/icon_star.png)
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
这个方法里面都是汇编指命,看到LOCK_IF_MP也有锁指令实现的原子操作,其实CAS也算是有锁操作,只不过是由CPU来触发,比synchronized性能好的多。
**********************学习笔记开始*********************************又开始深入学习了,大神就是这么厉害。写个demo测试下吧。跟大神一样就演示下AtomicInteger
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; public class CASTest { static int i=0; static AtomicInteger j=new AtomicInteger(0); public void count(){ i++; } public void safecount(){ j.getAndIncrement(); } public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub final CASTest c = new CASTest(); ExecutorService executorService = Executors.newFixedThreadPool(50); for(int k=0;k<100;k++) { executorService.execute( new Runnable(){ @Override public void run() { // TODO Auto-generated method stub for(int m=0;m<100;m++) { c.count(); c.safecount(); } } } ); } Thread.sleep(5000); System.out.println("castest 50 threads run add:"); System.out.println("count:"+i); System.out.println("safecount:"+j.get()); } }运行结果如下(每次结果可能不同):
![](http://img1.ph.126.net/33hgACn7ZbNFEljDAXBrTQ==/6598100009112245878.png)
可以看出并发情况下,不采用线程安全的类容易出错。当然这背后是基于cas的,上文已经介绍了。 知识点1多核cpu如何去实现“原子操作”。相关知识点:缓存行(cacheline)、CPU流水线(CPU line) 处理器保证系统从内存当中读取一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器是不能访问这个字节的地址的。最新的Intel X86能保证单处理器对同一缓存行里进行的16/32/64位操作是原子的。复杂的内存操作如跨总线宽度、跨缓存行,处理器通过总线锁定和缓存锁定来保证原子性。这两种机制Intel提供很多lock指令来实现,比如上文说的cmpxchg。 知识点2:JDK文档说cas同时具有volatile读和volatile写的内存语义。 针对上文说的cmpxchg指令,在多处理器下会加入LOCK前缀(LOCK cmpxchg),单处理器会忽略LOCK前缀。Intel对lock前缀有特殊说明: 1.根据内存区域不同提供总线锁定和缓存锁定。 2.禁止改指令与之前和之后的读指令和写指令重排序。3.把写缓冲区的数据全部刷新到内存中。2,、3点所具有的内存屏障效果,满足了volatile读和volatile写的内存语义。知识点3:CAS缺点:问题1:ABA问题因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
参考:http://ifeve.com/atomic-operation/
相关文章推荐
- 线程池学习-【转】一段JAVA线程池的设置引发的血案
- 关于JVM的堆栈的思考
- 学习JVM-运行时数据区
- java学习-jmap命令
- java学习-【转】使用Eclipse MAT查找内存泄漏工具介绍
- java学习-【转】JVM JSTAT命令的用法和参数讲解
- 【转】RPC技术简介
- java学习-【转】NIO DirectByteBuffer 内存泄露的测试
- 算法学习-【转】火车运煤算法--全面解析
- java学习-【转】NIO 水平触发,边缘触发的区别
- java学习-【转】Java常见内存溢出异常分析
- 【转】负载均衡的基本算法
- java学习-【转】什么是Reactor模式,或者叫反应器模式
- Python变量与数据类型
- 关于ListView的getItemViewType()这个方法的踩坑叙述
- 笔试题21 . LeetCode OJ (8)
- 京东VS猫宁,运费或将成为压垮京东的最后一根稻草
- SQL Server 用户名sa登陆出错
- git常用命令
- java学习-【转】【经验总结】NIO常见的陷阱解析