『图解Java并发』面试必问的CAS原理你会了吗?

在并发编程中我们都知道i++操作是非线程安全的,这是因为 i++操作不是原子操作。

如何保证原子性呢?常用的方法就是加锁。在Java语言中可以使用 SynchronizedCAS实现加锁效果。

Synchronized是悲观锁,线程开始执行第一步就是获取锁,一旦获得锁,其他的线程进入后就会阻塞等待锁。如果不好理解,举个生活中的例子:一个人进入厕所后首先把门锁上(获取锁),然后开始上厕所,这个时候有其他人来了只能在外面等(阻塞),就算再急也没用。上完厕所完事后把门打开(解锁),其他人就可以进入了。

CAS是乐观锁,线程执行的时候不会加锁,假设没有冲突去完成某项操作,如果因为冲突失败了就重试,最后直到成功为止。

什么是 CAS?

CAS(Compare-And-Swap)是比较并交换的意思,它是一条 CPU 并发原语,用于判断内存中某个值是否为预期值,如果是则更改为新的值,这个过程是原子的。下面用一个小示例解释一下。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,计算后要修改后的新值B。

(1)初始状态:在内存地址V中存储着变量值为 1。

(2)线程1想要把内存地址为 V 的变量值增加1。这个时候对线程1来说,旧的预期值A=1,要修改的新值B=2。

(3)在线程1要提交更新之前,线程2捷足先登了,已经把内存地址V中的变量值率先更新成了2。

(4)线程1开始提交更新,首先将预期值A和内存地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

(5)线程1重新获取内存地址 V 的当前值,并重新计算想要修改的新值。此时对线程1来说,A=2,B=3。这个重新尝试的过程被称为自旋。如果多次失败会有多次自旋。

(6)线程 1 再次提交更新,这一次没有其他线程改变地址 V 的值。线程1进行Compare,发现预期值 A 和内存地址 V的实际值是相等的,进行 Swap 操作,将内存地址 V 的实际值修改为 B。

总结:更新一个变量的时候,只有当变量的预期值 A 和内存地址 V 中的实际值相同时,才会将内存地址 V 对应的值修改为 B,这整个操作就是CAS

CAS 基本原理

CAS 主要包括两个操作:CompareSwap,有人可能要问了:两个操作能保证是原子性吗?可以的。

CAS 是一种系统原语,原语属于操作系统用语,原语由若干指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说 CAS 是一条 CPU 的原子指令,由操作系统硬件来保证。

在 Intel 的 CPU 中,使用 cmpxchg 指令。

回到 Java 语言,JDK 是在 1.5 版本后才引入 CAS 操作,在sun.misc.Unsafe这个类中定义了 CAS 相关的方法。

public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

可以看到方法被声明为native,如果对 C++ 比较熟悉可以自行下载 OpenJDK 的源码查看 unsafe.cpp,这里不再展开分析。

CAS 在 Java 语言中的应用

在 Java 编程中我们通常不会直接使用到 CAS,都是通过 JDK 封装好的并发工具类来间接使用的,这些并发工具类都在java.util.concurrent包中。

J.U.C 是java.util.concurrent的简称,也就是大家常说的 Java 并发编程工具包,面试常考,非常非常重要。

目前 CAS 在 JDK 中主要应用在 J.U.C 包下的 Atomic 相关类中。

比如说 AtomicInteger 类就可以解决 i++ 非原子性问题,通过查看源码可以发现主要是靠 volatile 关键字和 CAS 操作来实现,具体原理和源码分析后面的文章会展开分析。

CAS 的问题

CAS 不是万能的,也有很多问题。

敲黑板:CAS有哪些问题,这是面试高频考点,需要重点掌握

典型 ABA 问题

ABA 是 CAS 操作的一个经典问题,假设有一个变量初始值为 A,修改为 B,然后又修改为 A,这个变量实际被修改过了,但是 CAS 操作可能无法感知到。

如果是整形还好,不会影响最终结果,但如果是对象的引用类型包含了多个变量,引用没有变实际上包含的变量已经被修改,这就会造成大问题。

如何解决?思路其实很简单,在变量前加版本号,每次变量更新了就把版本号加一,结果如下:

最终结果都是 A 但是版本号改变了。

从 JDK 1.5 开始提供了AtomicStampedReference类,这个类的 compareAndSe方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

自旋开销问题

CAS 出现冲突后就会开始自旋操作,如果资源竞争非常激烈,自旋长时间不能成功就会给 CPU 带来非常大的开销。

解决方案:可以考虑限制自旋的次数,避免过度消耗 CPU;另外还可以考虑延迟执行。

只能保证单个变量的原子性

当对一个共享变量执行操作时,可以使用 CAS 来保证原子性,但是如果要对多个共享变量进行操作时,CAS 是无法保证原子性的,比如需要将 i 和 j 同时加 1:

i++;j++;

这个时候可以使用 synchronized 进行加锁,有没有其他办法呢?有,将多个变量操作合成一个变量操作。从 JDK1.5 开始提供了AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

有态度的总结

CAS 是 Compare And Swap,是一条 CPU 原语,由操作系统保证原子性。

Java语言从 JDK1.5 版本开始引入 CAS , 并且是 Java 并发编程J.U.C 包的基石,应用非常广泛。

当然 CAS 也不是万能的,也有很多问题:典型 ABA 问题、自旋开销问题、只能保证单个变量的原子性。

-- End --

Java 并发编程已经连续肝了几篇了,如果喜欢可以给公众号加个『星标』,后面第一时间看到系列文章。


往期推荐

ThreadLocal不好用?那是你没用对!


Semaphore自白:限流器用我就对了!


额!Java中用户线程和守护线程区别这么大?


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544807.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

美易官方:“圣诞老人行情”美股能否延续近期涨势?

圣诞老人行情”有望上演,美股能否延续近期涨势? 随着圣诞节的临近,市场开始期待所谓的“圣诞老人行情”能够上演。在过去的几年里,这个时期往往会出现一波上涨行情,给投资者带来一些安慰和喜悦。然而,今年的…

Linux高并发应用类型对系统内核的优化

Linux操作系统内核参数优化net.ipv4.tcp_max_tw_buckets 6000 net.ipv4.ip_local_port_range 1024 65000 net.ipv4.tcp_tw_recycle 1 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_syncookies 1 net.core.somaxconn 262144 net.core.netdev_max_backlog 262144 net.ipv4.tcp_m…

Java LinkedHashMap forEach()方法与示例

LinkedHashMap类的forEach()方法 (LinkedHashMap Class forEach() method) forEach() method is available in java.util package. java.util包中提供了forEach()方法 。 forEach() method is used to perform the specified action for every entry (key-value) pairs in the …

SimpleDateFormat线程不安全的5种解决方案!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)1.什么是线程不安全?线程不安全也叫非线程安全,是指多线程执行中,程序的执行结果和预期的…

Java LineNumberReader mark()方法与示例

LineNumberReader类mark()方法 (LineNumberReader Class mark() method) mark() method is available in java.io package. mark()方法在java.io包中可用。 mark() method is used to set the current position in this LineNumberReader stream and whenever we call to reset…

mac地址漂移flapping的前因后果

一、什么是mac地址flapping?mac地址漂移是指:在同一个vlan内,mac地址表项的出接口出现变更。如图:二、产生的原因1、因为环路或VRRP切换,导致的MAC地址漂移告警。(不予关注)2、因为无线用户漫游&#xff0…

时间转换竟多出1年!Java开发中的20个坑你遇到过几个?

前言最近看了极客时间的《Java业务开发常见错误100例》,再结合平时踩的一些代码坑,写写总结,希望对大家有帮助,感谢阅读~1. 六类典型空指针问题包装类型的空指针问题级联调用的空指针问题Equals方法左边的空指针问题ConcurrentHas…

android去掉顶部标题栏

在AndroidManifest.xml中实现&#xff1a; 注册Activity时加上如下的一句配置就可以实现。 <activity android:name".Activity"android:theme"android:style/Theme.NoTitleBar"></activity>

Oracle RAC Failover 详解

2019独角兽企业重金招聘Python工程师标准>>> Oracle RAC 同时具备HA(High Availiablity) 和LB(LoadBalance). 而其高可用性的基础就是Failover(故障转移). 它指集群中任何一个节点的故障都不会影响用户的使用&#xff0c;连接到故障节点的用户会被自动转移到健康节…

超级详细的Spring Boot 注解总结

日常编程中我相信大家肯定都用过spring&#xff0c;也用过spring的注解&#xff0c;哪怕面试的时候也经常会被问到一些spring和spring boot注解的作用和含义等&#xff0c;那么这篇就带大家来看看超级详细的Spring Boot 注解总结&#xff01;搞起!我们先来看看本篇会讲到的注解…

Android下 布局加边框 指定背景色 半透明

文章转自&#xff1a;http://www.cnblogs.com/bavariama/archive/2013/09/25/3338375.html 背景设置为自定义的shape文件&#xff1a; <!-- <?xml version"1.0" encoding"utf-8"?> <shape xmlns:android"http://schemas.android.com/a…

inputstream示例_Java InputStream available()方法与示例

inputstream示例InputStream类的available()方法 (InputStream Class available() method) available() method is available in java.io package. available()方法在java.io包中可用。 available() method is used to return the number of available bytes left for reading …

json前后台传值

谈到JSON,简单的说就是一种数据交换格式。近年来&#xff0c;其在服务器之间交换数据的应用越来越广&#xff0c;相比XML其格式更简单、编解码更容易、扩展性更好&#xff0c;所以深受开发人员的喜爱。 下面简单的写一下在项目中前后台json传值的一个小例子&#xff0c;供大家参…

一个ThreadLocal和面试官大战30个回合

开场杭州某商务楼里&#xff0c;正发生着一起求职者和面试官的battle。面试官&#xff1a;你先自我介绍一下。安琪拉&#xff1a;面试官你好&#xff0c;我是草丛三婊&#xff0c;最强中单&#xff08;妲己不服&#xff09;&#xff0c;草地摩托车车手&#xff0c;第21套广播体…

ASP.NET 网站项目 EF 的简单操作例子

ASP.NET 网站项目 EF 的简单操作例子&#xff1a;操作代码&#xff1a;using EFTest.Models; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Web; using System.Web.Mvc;namespace EFTest.Controllers {public class D…

java enummap_Java EnumMap containsValue()方法与示例

java enummapEnumMap类containsValue()方法 (EnumMap Class containsValue() method) containsValue() method is available in java.util package. containsValue()方法在java.util包中可用。 containsValue() method is used to check whether the given value element (val_…

mysql主主互备架构

mysql主主互备架构企业级mysql集群具备高可用&#xff0c;可扩展性&#xff0c;易管理&#xff0c;低成本的特点。mysql主主互备就是企业中常用的一个解决方案。在这种架构中&#xff0c;虽然互为主从&#xff0c;但同一时刻只有一台mysql 可读写&#xff0c;一台mysqk只能进行…

图文并茂的聊聊Java内存模型!

在面试中&#xff0c;面试官经常喜欢问&#xff1a;『说说什么是Java内存模型(JMM)&#xff1f;』面试者内心狂喜&#xff0c;这题刚背过&#xff1a;『Java内存主要分为五大块&#xff1a;堆、方法区、虚拟机栈、本地方法栈、PC寄存器&#xff0c;balabala……』面试官会心一笑…

JS根据文本框内容匹配并高亮显示

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script type"text/javascript">function SearchCompare() {// 获取搜索字符串var searchText document.getElementById("SearchCompa…

AngularJS入门心得2——何为双向数据绑定

前言&#xff1a;谁说Test工作比较轻松&#xff0c;最近在熟悉几个case&#xff0c;差点没疯。最近又是断断续续的看我的AngularJS&#xff0c;总觉得自己还是没有入门&#xff0c;可能是自己欠前端的东西太多了&#xff0c;看不了几行代码就有几个常用函数不熟悉的。看过了大漠…