201709021工作日记--CAS解读
CAS主要参考博文:classtag http://www.jianshu.com/p/473e14d5ab2d
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术 。Compare and Swap, 翻译成比较并交换
。 简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。
java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包。可见CAS
的重要性。java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁
。
1.CAS操作
我们常常做这样的操作
if(a==b) {
a++;
}
试想一下如果在做a++之前a的值被改变了怎么办?a++还执行吗?出现该问题的原因是在多线程环境下,a的值处于一种不定的状态。采用锁可以解决此类问题,但CAS也可以解决,而且可以不加锁。
int expect = a;
if(a.compareAndSet(expect,a+1)) {
doSomeThing1();
} else {
doSomeThing2();
}
这样如果a的值被改变了a++就不会被执行。按照上面的写法,a!=expect之后,a++就不会被执行,如果我们还是想执行a++操作怎么办,没关系,可以采用while循环
while(true) {
int expect = a;
if (a.compareAndSet(expect, a + 1)) {
doSomeThing1();
return;
} else {
doSomeThing2();
}
}
采用上面的写法,在没有锁的情况下实现了a++操作,这实际上是一种非阻塞算法。
2.CAS应用
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
非阻塞算法:一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
private volatile int value;
首先毫无以为,在没有锁的机制下可能需要借助volatile原语,保证线程间的数据是可见的(共享的)。
这样才获取变量的值的时候才能直接读取。
public final int get() {
return value;
}
然后来看看++i是怎么做到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而compareAndSet利用JNI来完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。
其中unsafe.compareAndSwapInt(this, valueOffset, expect, update)
类似:
if (this == expect) {
this = update
return true;
} else {
return false;
}
那么问题就来了,成功过程中需要2个步骤:比较this == expect,替换this = update,compareAndSwapInt如何这两个步骤的原子性呢? 参考CAS的原理。
4.Concurrent包的实现
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
首先,声明共享变量为volatile;
然后,使用CAS的原子条件更新来实现线程之间的同步;
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
5.CAS的缺点
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
(1)ABA问题
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
(2)循环时间长开销大
(3)只能保证一个共享变量的原子操作
总结
可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。
a
201709021工作日记--CAS解读的更多相关文章
- 201709021工作日记--Volley源码解读(四)
接着volley源码(三)继续,本来是准备写在(三)后面的,但是博客园太垃圾了,写了半天居然没保存上,要不是公司这个博客还没被限制登陆,鬼才用这个...真是垃圾 继续解读RequestQueue的源码 ...
- 201709021工作日记--Volley源码详解(五)
学习完了CacheDispatcher这个类,下面我们看下NetworkDispatcher这个类的具体细节,先上代码: /** * 提供一个线程执行网络调度的请求分发 * Provides a th ...
- 201709015工作日记--IntentService使用
一.IntentService与Service的区别 Service 是 Android 四大组件之一,正常来说,我们直接使用 Service 就可以了. 但是 Service 存在几个问题: 默认不 ...
- 201709013工作日记--Android消息机制HandlerThread
1.首先来看一个常规的handler用法: 在主线程中建立一个handler: private Handler mHandler = new Handler() { @Override public ...
- 201709012工作日记--activity与service的通信机制
service生命周期 Service主要包含本地类和远程类. Service不是Thread,Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 ...
- 201709011工作日记--ART与Dalvik&&静态类与非静态类
1.ART 与 Dalvik 的优缺点对比 什么是Dalvik:Dalvik是Google公司自己设计用于Android平台的Java虚拟机.dex格式是专为Dalvik应用设计的一种压缩格.Dalv ...
- 201709011工作日记--Volley源码详解(二)
1.Cache接口和DiskBasedCache实现类 首先,DiskBasedCache类是Cache接口的实现类,因此我们需要先把Cache接口中的方法搞明白. 首先分析下Cache接口中的东西, ...
- 20170908工作日记--Volley源码详解
Volley没有jar包,需要从官网上下载源码自己编译出来,或者做成相关moudle引入项目中.我们先从最简单的使用方法入手进行分析: //创建一个网络请求队列 RequestQueue reques ...
- 20170906工作日记--volley源码的相关方法细节学习
1. 在StringRequest类中的75行--new String();使用方法 /** * 工作线程将会调用这个方法 * @param response Response from the ne ...
随机推荐
- RabbitMq install on Centos
安装服务(root) erlang官方安装说明:https://www.erlang-solutions.com/resources/download.html step 1: 安装erlang的yu ...
- 书单BookList
1. <代码大全> 史蒂夫·迈克康奈尔 (Code Complete) 2. <程序员修炼之道> Andrew Hunt [读过了,非常好的一本书] (Pragmatic Pr ...
- Boost.Coroutine2:学习使用Coroutine(协程)
function(函数)routine(例程)coroutine (协程) 函数,例程以及协程都是指一系列的操作的集合. 函数(有返回值)以及例程(没有返回值)也被称作subroutine(子例程), ...
- HTML实现文件拖动上传
在大型企业的开发过程中,很多比较有趣而实际的功能往往都是让大家望而却步,我给大家带来一个百度云盘和360云盘的HTML5多文件拖动上传技术: 1:记得导入:common-fileupload.jar包 ...
- ndarray
ndarray ndarray-多维数组对象 ndarray数组分两部分 实际数据 描述数据的元数据(数据类型/维度等) ndarray所有元素类型相同,下标从0开始 import numpy as ...
- ingress 密码验证
traefik ingress 上面的方式需要引入haprox或者nginx,多引入了一个代理转发层,其实ingress本身就提供了basic auth的支持,在ingress规则中添加额外的认证an ...
- VMware安装win7:units specified don't exist问题
主要是磁盘接口不匹配,调整CD/DVD和硬件磁盘接口, CD/DVD调整成IDE,硬盘调整成SATA即可. 提示system not found,主分区没有激活,进入disgenius,会提示修正,保 ...
- django做form表单的数据验证
我们之前写的代码都没有对前端input框输入的数据做验证,我们今天来看下,如果做form表单的数据的验证 在views文件做验证 首先用文字描述一下流程 1.在views文件中导入forms模块 2. ...
- phpcms如何给已有的模块添加新功能?
phpcms如何给已有的模块添加新功能? 方法一:直接在模块里的控制器文件中添加功能. 不建议使用此方法,因为一旦phpcms升级,有可能会覆盖模块中的文件, 导致你添加的功能丢失. 方法二:新建一个 ...
- 从输入url到显示网页发生了什么
原文链接:https://juejin.im/post/5bf23afa6fb9a049be5d1494 在浏览器中输入url到显示网页主要包含两个部分: 网络通信和页面渲染 互联网内各网络设备间的通 ...