CAS(比较并交换)
一、CAS(无锁的执行者)
CAS包含3个参数:内存值 V 旧的预期值 A 新值 B
当且仅当V值等于A值时,将V的值改为B值,如果V值和A值不同,说明已经有其他线程做了更新,则当前线程什么都不做,最后返回当前V的真实值。CAS操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功地完成操作。
当多个线程同时使用CAS同时操作同一个变量时,只有其中一个线程会胜出并成功更新,其余均会失败;但失败的线程并不会挂起,仅是被告知失败,并且允许再次尝试,也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,同样知道其他线程对共享资源操作的影响,并执行相应的处理措施。CAS的关键点在于,系统在硬件层面保证了比较并交换操作的原子性,处理器使用基于对缓存加锁或总线枷锁的方式来实现多处理器之间的原子操作。
由于是无锁操作,因此不可能出现死锁情况。CAS是非阻塞算法的一种常见实现。CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
一个线程间共享的变量,首先在主存中会保留一份,然后每个线程的工作内存也会保留一份副本。这里说的预期值,就是线程保留的副本。当该线程从主存中获取该变量的值后,主存中该变量可能已经被其他线程刷新了,但是该线程工作内存中该变量却还是原来的值,这就是所谓的预期值了。当你要用CAS刷新该值的时候,如果发现线程工作内存和主存中不一致了,就会失败,如果一致,就可以更新成功。
CAS的优缺点
CAS由于是在硬件层面保证的原子性,不会锁住当前线程,它的效率是很高的。
CAS虽然很高效地实现了原子操作,但它依然存在三个问题。如下:
ABA问题
CAS会导致“ABA问题”。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差中可能导致数据发生变化。
比如一个线程one从内存位置V中取出A,这是另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号, 一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销大
自选CAS如果长时间不成功,会给CPU带来非常大的执行开销。因此CAS不适合竞争十分频繁的场景。
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。
这里粘贴一个,模拟CAS实现的计数器:
- public class CASCount implements Runnable{
- private SimilatedCAS counter = new SimilatedCAS();
- public void run(){
- for(int i=0;i<10000;i++){
- System.out.println(this.increment());
- }
- }
- public int increment(){
- int oldValue = counter.getValue();
- int newValue = oldValue + 1;
- while(!counter.compareAndSwap(oldValue,newValue)){
- //如果CAS失败,就去拿新值继续执行CAS
- oldValue = counter.getValue();
- newValue = oldValue + 1;
- }
- return newValue;
- }
- public static void main(String[] args){
- Runnable run = new CASCount();
- new Thread(run).start();
- new Thread(run).start();
- new Thread(run).start();
- new Thread(run).start();
- new Thread(run).start();
- }
- }
- class SimilatedCAS{
- private int value;
- public int getValue(){
- return value;
- }
- //这里只能用synchronized了,毕竟无法调用操作系统的CAS
- public synchronized boolean compareAndSwap(int expectedValue,int newValue){
- if(value == expectedValue){
- value = newValue;
- return true;
- }
- return false;
- }
- }
二、原子包 java.util.concurrent.atomic
JDK1.5的原子包:java.util.concurrent.atomic这个包里面提供了一组原子类。其基本特性是:在多线程环境下,当有多个线程同时执行这些类实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择另一个线程进入。相对于synchronized这种阻塞算法,Atomic类在软件层面上是非阻塞的,它的原子性其实是在硬件层面上借助相关的指令来保证的,由于一般CPU切换时间比CPU指令集操作更加长,所以JUC在性能上有了很大的提升。
AtomicInteger是一个支持原子操作的Integer类,就是保证对AtomicInteger类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致的问题。如果不使用AtomicInteger,要实现一个按顺序获取的ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的ID的现象。Java并发库中的AtomicXXX类均是基于这个原语的实现,拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的:
来看看++i是怎么做到的。如下代码:
- public class AtomicInteger extends Number implements java.io.Serializable {
- private volatile int value;
- public final int get() {
- return value;
- }
- public final int getAndIncrement() {
- for (;;) { //CAS 自旋,一直尝试,直达成功
- int current = get();
- int next = current + 1;
- if (compareAndSet(current, next))
- return current;
- }
- }
- public final boolean compareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
- }
在这里getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作,非阻塞算法。
其中,unsafe.compareAndSwapInt()是一个native方法,正是调用CAS原语完成该操作。
首先假设有一个变量i,i的初始值为0。每个线程都对i进行+1操作。CAS是这样保持同步的:
假设有两个线程,线程1读取内存中的值为0,current=0,next=1,然后挂起,然后线程2对i进行操作,将i的值变成了1。线程2执行完,回到线程1,进入if里的compareAndSet方法,该方法进行的操作的逻辑是,(1)如果操作数的值在内存中没有被修改,返回true,然后compareAndSet方法返回next的值;(2)如果操作数的值在内存中被修改了,则返回false,重新进入下一次循环,重新得到current的值为1,next的值为2,然后再比较,由于这次没有被修改,所以直接返回2。
那么为什么自增操作要通过CAS来完成?仔细观察getAndIncrement()方法,发现自增操作其实拆成了两步完成的:
int current = get();
int next = current +1;
由于volatile只能保证读取或写入的是最新值,那么可能出现以下情况:
1)A线程执行get()操作,获取current值(假设为1)
2)B线程执行get()操作,获取current值(为1)
3)B线程执行next = current +1操作,next=2
4)A线程执行next = current +1操作,next=2
这样的结果明显不是我们想要的,所以,自增操作必须采用CAS来完成。
CAS(比较并交换)的更多相关文章
- CAS 比较并交换
简介 CAS 的全称为 Compare-And-Swap,他是一条 CPU 并发源语. 他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的. CAS 并发原语体现在 J ...
- 原子类型的使用&Unsafe&CAS
在项目中也经常可以见到原子类型(AtomicXXX)的使用,而且AtomicXXX常用来代替基本类型或者基本类型的包装类型,因为其可以在不加同步锁的情况下保证线程安全(只对于原子操作). 下面以Ato ...
- DLC双端锁,CAS,ABA问题
一.什么是DLC双端锁?有什么用处? 为了解决在多线程模式下,高并发的环境中,唯一确保单例模式只能生成一个实例 多线程环境中,单例模式会因为指令重排和线程竞争的原因会出现多个对象 public cla ...
- CAS 分析
CAS是什么 (1) CAS(Compare and Swap) 比较并交换, 比较并交换是在多线程并发时用到的一种技术 (2) CAS是原子操作, 保证并发安全性, 而不是保证并发同步. (3) C ...
- CAS原理解析
CAS底层原理 概念 CAS的全称是Compare-And-Swap,它是CPU并发原语 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的 CAS并发原语体现在Jav ...
- 基础篇:详解锁原理,volatile+cas、synchronized的底层实现
目录 1 锁的分类 2 synchronized底层原理 3 Object的wait和notify方法原理 4 jvm对synchronized的优化 5 CAS的底层原理 6 CAS同步操作的问题 ...
- ReentrantLock锁-CAS与阻塞
ReentrantLock锁 ReentrantLock通过原子操作和阻塞实现锁原理,一般使用lock获取锁,unlock释放锁 lock的时候可能被其他线程获得所,那么此线程会阻塞自己,关键原理底层 ...
- CAS你知道吗?底层如何实现?ABA问题又是什么?关于这些你知道答案吗
CAS你知道吗?如何实现? 1. compareAndSet 在volatile当中我们提到,volatile不能保证原子语义,所以当用到变量自增时,如果用到synchronized会太"重 ...
- ConcurrentLinkedQueue代码解析
原因:学习ConcurrentLinkedQueue是看到akka框架的默认邮箱是使用ConcurrentLinkedQueue实现的. 1. ConcurrentLinkedQueue在java.u ...
- java并发编程(8)原子变量和非阻塞的同步机制
原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用 ...
随机推荐
- Web安全测试学习笔记-DVWA-SQL注入-1
SQL注入的定义网上很多,作为一个初学者,我对SQL注入的理解是这样的:网站应用一般都有后台数据库(不论是关系型还是非关系型),用户在网站上的绝大部分操作,最终都会跟数据库交互(也就是执行一串SQL语 ...
- gitlab runner使用docker报错(x509: certificate signed by unknown authority)定位
如果gitlab runner使用docker,docker是普通配置,配置好后,runner就可以正常执行任务了. 另外一个环节Docker配置了tls加密连接,添加runner后,runner的配 ...
- 资深程序员告诉你为什么要用Python3而不是Python2
经常遇到这样的问题:<现在开始学习python的话,是学习python2.x还是学习python3.x比较好?>,这也是许多初学者会遇到的问题,我们的答案是python 3.x. 为了帮助 ...
- 添加商品到购物车add_to_cart.php(学生笔记)
<?php session_start();//启动session $goods_id = $_POST['goods_id'];//获取商品id $number = $_POST['num'] ...
- javaWeb核心技术第四篇之Javascript第二篇事件和正则表达式
- 事件 - 表单提交(掌握) "onsubmit" - 单击事件(掌握) "onclick" - 页面加载成功事件(掌握) "onload" ...
- easyui-datagrid 使用笔记
1.清空所有行和所有列 $('#dgJGQuery').datagrid({ columns: [], url: '', data: [] }); 2.采用 datagrid-groupview 分组 ...
- js实现防抖函数和节流函数
防抖函数(debounce) 含义:防抖函数指的是在特定的时间内没有再次触发,才得以进行接下来的函数运行: 用途:当window.onresize不断的调整大小的时候,为了避免不断的重排与重绘,可以用 ...
- [转]How to mouse hover using Blue prism on a web page
本文转自:https://stackoverflow.com/questions/53126436/how-to-mouse-hover-using-blue-prism-on-a-web-page/ ...
- WebGL-3D地图大俯仰角的雾化处理
腾讯位置服务Javascript API GL版,是基于WebGL技术打造的地图API库,使得浏览器环境下也可实现APP端的应用体验,提供2D/3D模式,运行流畅.当前版本提供地图展示.标记.信息窗口 ...
- mssql 系统函数 字符串函数 space 功能简介
转自: http://www.maomao365.com/?p=4672 一.space 函数功能简介 space功能:返回指定数量的空格参数简介: 参数1: 指定数量,参数需为int类型 注意事项 ...