文档章节

非阻塞同步之 CAS

长安一梦
 长安一梦
发布于 06/23 10:16
字数 1353
阅读 14
收藏 0
点赞 0
评论 0

为解决线程安全问题,互斥同步相当于以时间换空间。多线程情况下,只有一个线程可以访问同步代码。这种同步也叫阻塞同步(Blocking Synchronization).

这种同步属于一种悲观并发策略。认为只要不同步,共享数据就会被并发访问。随着硬件指令集的发展,我们可以采用基于冲突检测的乐观并发策略。

先进行操作,如果没有其他线程操作共享数据,就操作成功;否则采取补偿措施,去重试直到成功。这种策略不需要把线程挂起,因此称为非阻塞同步。(Non-Blocking Synchrinization)

要保证两个操作的原子性,需要借助处理器指令来完成。这类指令有:

  1. 测试并设置(test-and-set)

  2. 获取并增加(fetch-and-increment)

  3. 交换(swap)

  4. 比较并交换(compare-and-swap,简称 CAS)

  5. 加载链接、条件存储(load-linked/store-conditional)

CAS 指令需要有 3 个操作数,分别为内存位置(V,变量的内存地址),旧的预期值(A),和新值(B)。CAS 指令执行时,当且仅当 V 的值复合旧的预期值时,处理器使用 B 更新 V 值。否则不更新,上述操作是一个原子操作。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。

Java 中的 CAS 操作:

public class CasTest {

    //private Integer count = 0;
    private final AtomicInteger count = new AtomicInteger(0);


    private ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 20,
            10, TimeUnit.MINUTES, new ArrayBlockingQueue<Runnable>(100));

    private void increase() {
        // count++;
        count.incrementAndGet();
    }

    @Test
    public void testMutiThreadAdd() {
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                for (int j = 0; j < 1000; j++) {
                    increase();
                }
            });
        }

        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count);
    }

}

使用 atomicInteger 后,每次都能输出一致的结果。increamentAndGet( ) 通过 CAS 保证了 自增操作的原子性;

CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

1.  ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作.

参考: 深入理解Java 虚拟机

© 著作权归作者所有

共有 人打赏支持
长安一梦
粉丝 1
博文 29
码字总数 24421
作品 0
杭州
其他
非阻塞同步算法与CAS(Compare and Swap)无锁算法

锁(lock)的代价 锁是用来做并发最简单的方式,当然其代价也是最高的。内核态的锁的时候需要操作系统进行一次上下文切换,加锁、释放锁会导致比较多的上下文切换和调度延时,等待锁的线程会...

空云万里晴
2016/06/16
63
0
无锁的同步策略——CAS操作详解

1. 从乐观锁和悲观锁谈起 乐观锁和悲观锁是两种不同的解决并发问题的策略。悲观锁策略假定任何一次并发都会发生冲突,所以总是采用最严格的方式来进行并发控制。java中的独占锁(synchronized...

takumiCX
07/14
0
0
volatile原理与技巧--Java

为什么使用volatile比同步代价更低? 同步的代价, 主要由其覆盖范围决定, 如果可以降低同步的覆盖范围, 则可以大幅提升程序性能. 而volatile的覆盖范围仅仅变量级别的. 因此它的同步代价很低....

刘小兵2014
2010/12/07
0
0
线程安全 4/26/2016

实现可见性:volatile, synchronized, final volatile 两层语义 不适用:作为自增长值 适用:作为状态值 互斥同步(阻塞同步) synchronized 非阻塞同步 CAS(compareAndSet)...

jayronwang
2016/04/26
16
0
java AQS的实现原理(大部分同步类都依赖AQS实现)

谈到并发,不得不谈;而谈到,不得不谈AbstractQueuedSynchronized(AQS)!,类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如...

激情的狼王丶21
2017/12/27
0
0
JAVA 并发编程实践 - 原子变量与非阻塞同步机制 笔记

非阻塞算法: 利用底层的源自机器指令(比如CAS)代替锁来实现数据在并发访问中的一致性。应用于:操作系统和JVM中实现线程/进程调度机制,垃圾回收机制以及锁和其他并发数据结构。 与基于锁的方...

pczhangtl
2014/01/20
0
1
同步,异步,阻塞,非阻塞的Java例子

定义:任务A,任务B 同步:任务A和任务B之间有关联,例如任务B中途要给任务A一个数字,那么任务A或许需要等待任务B生产这个数,任务A需要等待任务B的这个动作叫做同步。 异步:事件A和事件B...

JoshuaShaw
2016/03/30
2.6K
2
Java 并发:第六部分 - 原子变量

当几個线程都不能访问某個数据(通常是某個变量)时,你必须同步数据的访问以确保变量的可见性和正确性。举個例子,如果你有如下壹個简单的计数器(是的,我们又壹次举了这個例子): public...

苗哥
2013/10/07
0
0
非阻塞算法

原文地址 作者:Jakob Jenkov 译者:张坤 在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法。在绝大多数项目中,在算法中如果一个线程的挂起没有导致其它...

暗之幻影
2016/12/20
3
0
29、Java并发性和多线程-非阻塞算法

以下内容转自http://ifeve.com/non-blocking-algorithms/: 在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法。在绝大多数项目中,在算法中如果一个线程的...

easonjim
2017/06/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

java 重写排序规则,用于代码层级排序

1.dataList 是个List<Map<String,Object>> 类型的数据,所以比较的时候是冲map中获取数据,并且数据不能为空。 2.dataList 类型是由自己定义的,new Comparator<Map<String,Object>> 也是对应......

轻量级赤影
7分钟前
0
0
分布式大型互联网企业架构!

摘要: 开发工具 1.Eclipse IDE:采用Maven项目管理,模块化。 2.代码生成:通过界面方式简单配置,自动生成相应代码,目前包括三种生成方式(增删改查):单表、一对多、树结构。生成后的代码...

明理萝
7分钟前
0
1
对MFC程序的一点逆向分析:定位按钮响应函数的办法

因为消息响应函数保存在AFX_MSGMAP_ENTRY数组中, 观察nMessage、nCode、nID、pfn利用IDA在rdata段中搜索即可, 在IDA中找到代码段基址0x401000,函数地址0x403140, 在WinDbg中运行!addre...

oready
8分钟前
0
0
阻抗匹配与史密斯(Smith)圆图基本原理

参考:http://bbs.eeworld.com.cn/thread-650695-1-1.html

whoisliang
13分钟前
0
0
maven配置文件分离

一、 简介 遇到很多次别人处理的项目,测试环境,本地开发和线上环境的配置不一样,每一次部署都要重新修改配置文件,提交审核代码,才能打包,非常不方便。 其实相信很多人都知道可以使用m...

trayvon
13分钟前
0
0
MacOS和Linux内核的区别

导读 有些人可能认为MacOS和Linux内核有相似之处,因为它们可以处理类似的命令和类似的软件。甚至有人认为苹果的MacOS是基于linux的。事实上,这两个内核的历史和特性是非常不同的。今天,我...

问题终结者
29分钟前
1
0
SpringBoot | 第八章:统一异常、数据校验处理

前言 在web应用中,请求处理时,出现异常是非常常见的。所以当应用出现各类异常时,进行异常的捕获或者二次处理(比如sql异常正常是不能外抛)是非常必要的,比如在开发对外api服务时,约定了响...

oKong
37分钟前
2
0
mysql高级

一、存储引擎 InnoDB MyISAM 比较 二、数据类型 整型 浮点数 字符串 时间和日期 三、索引 索引分类 索引的优点 索引优化 B-Tree 和 B+Tree 原理 四、查询性能优化 五、切分 垂直切分 水平切分...

丁典
58分钟前
1
0
rsync通过同步服务、系统日志、screen工具

rsync通过后台服务同步 在远程主机中建立一个rsync服务器,在服务器上配置好rsync的各种应用,然后将本机作为rsync的一个客户端连接远程的rsync服务器。 首先在A机器上建立并且配置rsync的配...

黄昏残影
今天
5
0
Spring Cloud Gateway 接口文档聚合实现

在微服务架构下,通常每个微服务都会使用Swagger来管理我们的接口文档,当微服务越来越多,接口查找管理无形中要浪费我们不少时间,毕竟懒是程序员的美德。 由于swagger2暂时不支持webflux 走...

冷冷gg
今天
150
2

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部