五、原子操作(CAS)
原子操作(CAS)
一、CAS(Compare And Set)
Compare And Set(或Compare And Swap),CAS是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)、新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
在java中可以通过锁和循环CAS的方式来实现原子操作。Java中 java.util.concurrent.atomic包相关类就是 CAS的实现,atomic包里包括以下类:
| AtomicBoolean | 可以用原子方式更新的 boolean 值。 |
| AtomicInteger | 可以用原子方式更新的 int 值。 |
| AtomicIntegerArray | 可以用原子方式更新其元素的 int 数组。 |
| AtomicIntegerFieldUpdater | 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。 |
| AtomicLong | 可以用原子方式更新的 long 值。 |
| AtomicLongArray | 可以用原子方式更新其元素的 long 数组。 |
| AtomicLongFieldUpdater | 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。 |
| AtomicMarkableReference | AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。 |
| AtomicReference | 可以用原子方式更新的对象引用。 |
| AtomicReferenceArray | 可以用原子方式更新其元素的对象引用数组。 |
| AtomicReferenceFieldUpdater<T,V> | 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。 |
| AtomicStampedReference | AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。 |
二、AtomicInteger
AtomicInteger可以用原子方式更新的 int 值。AtomicInteger 可用在应用程序中(如以原子方式增加的计数器),并且不能用于替换 Integer。但是,此类确实扩展了 Number,允许那些处理基于数字类的工具和实用工具进行统一访问。 我们拿 AtomicInteger为例来学习下 CAS操作是如何实现的。
通常情况下,在 Java中,i++等类似操作并不是线程安全的,因为 i++可分为三个独立的操作:获取变量当前值,为该值+1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作时“原子性”的。但是利用加锁的方式来实现该功能的话,代码将非常复杂及难以维护,如:
synchronized (lock) {
i++;
}
相关类中还需要增加 Object lock等额外标志,这样就带来了很多麻烦,增加了很多业务无关代码,给开发与维护带来了不便。
然而利用 atomic包中相关类型就可以很简单实现此操作,以下是一个计数程序实例:
public class Counter {
private AtomicInteger ai = new AtomicInteger();
private int i = 0;
public static void main(String[] args) {
final Counter cas = new Counter();
List<Thread> threads = new ArrayList<Thread>();
// 添加100个线程
for (int j = 0; j < 100; j++) {
threads.add(new Thread(new Runnable() {
public void run() {
// 执行100次计算,预期结果应该是10000
for (int i = 0; i < 100; i++) {
cas.count();
cas.safeCount();
}
}
}));
}
//开始执行
for (Thread t : threads) {
t.start();
}
// 等待所有线程执行完成
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("非线程安全计数结果:"+cas.i);
System.out.println("线程安全计数结果:"+cas.ai.get());
}
/** 使用CAS实现线程安全计数器 */
private void safeCount() {
for (;;) {
int i = ai.get();
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值
boolean suc = ai.compareAndSet(i, ++i);
if (suc) {
break;
}
}
}
/** 非线程安全计数器 */
private void count() {
i++;
}
}
/**
非线程安全计数结果:9942
线程安全计数结果:10000
*/
其中非线程安全计数器所计算的结果每次都不相同且不正确,而线程安全计数器计算的结果每次都是正确的。
三、存在的问题
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题:ABA问题、循环时间长开销大、只能保证一个共享变量的原子操作。
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的执行效率。
只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
五、原子操作(CAS)的更多相关文章
- java并发编程系列二:原子操作/CAS
什么是原子操作 不可被中断的一个或者一系列操作 实现原子操作的方式 Java可以通过锁和循环CAS的方式实现原子操作 CAS( Compare And Swap ) 为什么要有CAS? Compar ...
- Java并发编程(十一)——原子操作CAS
一.原子操作 syn基于阻塞的锁的机制,1.被阻塞的线程优先级很高,2.拿到锁的线程一直不释放锁怎么办?3.大量的竞争,消耗cpu,同时带来死锁或者其他安全. CAS的原理 CAS(Compare A ...
- java原子操作CAS
本次内容主要讲原子操作的概念.原子操作的实现方式.CAS的使用.原理.3大问题及其解决方案,最后还讲到了JDK中经常使用到的原子操作类. 1.什么是原子操作? 所谓原子操作是指不会被线程调度机制打断的 ...
- 原子操作cas
一.概念, 基于处理器指令,把比较和交换合成一个指令完成,保证了原子性: 因为是针对一个内存地址值的,一个内存地址指向一个变量,所以只对一个共享变量能保证原子性: 二.原子操作类 锁只有synchro ...
- hbase 原子操作cas
在高并发的情况下,对数据row1 column=cf1:qual1, timestamp=1, value=val1的插入或者更新可能会导致非预期的情况, 例如:原本客户端A需要在value=val ...
- 并发之ATOMIC原子操作--CAS乐观锁原理(二)
1.乐观锁介绍 程序完成并发操作时,访问数据时每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止.就是当去做某个修改或其他操作的时候它认为不会有其他线程来做同样的操作(竞争) ...
- CAS 原子操作
理会CAS和CAS: 有时候面试官面试问你的时候,会问,谈谈你对CAS的理解,这时应该有很多人,就会比较懵,当然,我也会比较懵,当然我和很多人的懵不同,很多人可能,并不知道CAS是一个什么东西,而在我 ...
- 并发编程--CAS自旋锁
在前两篇博客中我们介绍了并发编程--volatile应用与原理和并发编程--synchronized的实现原理(二),接下来我们介绍一下CAS自旋锁相关的知识. 一.自旋锁提出的背景 由于在多处理器系 ...
- 原子操作CAS-最小的线程安全
原文连接:(http://www.studyshare.cn/blog-front/blog/details/1166/0 )一.原子操作是什么? 如果有两个线程分别执行两个操作A和B,从第一个线程执 ...
- 原子操作&普通锁&读写锁
一:原子操作CAS(compare-and-swap) 原子操作分三步:读取addr的值,和old进行比较,如果相等,则将new赋值给*addr,他能保证这三步一起执行完成,叫原子操作也就是说它不能再 ...
随机推荐
- idea插件备份
- KeContextToKframes函数逆向
在逆向_KiRaiseException(之后紧接着就是派发KiDispatchException)函数时,遇到一个 KeContextToKframes 函数,表面意思将CONTEXT转换为 TRA ...
- python3报'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)解决方法
运行前指定export PYTHONIOENCODING为utf-8 如 export PYTHONIOENCODING=utf-8; python main.;y
- SQL server已经设置为单用户模式,还是无法做分离、属性设置等操作
https://www.cnblogs.com/xingyunqiu/p/10336938.html SQL server已经设置为单用户模式,Sql server还原失败数据库正在使用,无法获得对数 ...
- C#A类派生类强转基类IL居然还是可以调用派生类中方法的例子
大家都知道在C#中,如果B类继承自A类,如果一个对象是B类型的但是转换为A类型之后,这个对象是无法在调用属于B类型的方法的,如下例子: 基类A: public class A { } 派生类B: pu ...
- 这篇文章带你彻底理解synchronized
本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...
- indexOf()字符位置
package seday01; /** * int indexOf(String str) * 查找给定字符串在当前字符串中的位置,若返回值为-1,则 * 表示当前字符串中不含有给定的内容. * @ ...
- PHP面试题2019年阿里巴巴面试题及答案解析
一.单选题(共27题,每题5分) 1.Memcache与Redis的比较错误的是? A.Memcache过期后,不删除缓存,会导致下次取数据数据的问题,Redis有专门线程,清除缓存数据: B.Mem ...
- 科技风商务项目管理PPT模板
模板来源:http://ppt.dede58.com/peixunyanjiang/26267.html
- [b0042] python 归纳 (二七)_gui_tkinter_基本使用
# -*- coding: utf-8 -*- """ 学习 Tkinter画图基本控件使用 逻辑: 放几个 输入控件.点击按钮,将输入控件内容打印出来 使用: 1. 创 ...