Java并发--Java中的CAS操作和实现原理
这几天准备梳理一下Java多线程和并发的相关知识,主要是系统的梳理一下J.U.C包里的一些东西,特别是以前看过很多遍的AQS和实现类,还有各种并发安全的集合类。最重要的就是这个CAS操作,可以说是整个J.U.C包的灵魂之处。
1.什么是CAS?
CAS:Compare and Swap, 翻译成比较并交换。
看到这个定义,可以说是没有任何意义的一句话,但是确实最能概括CAS操作过程的一句话。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
以下这段JAVA代码,基本上反映了CAS操作的过程。但是请注意,真实的CAS操作是由CPU完成的,CPU会确保这个操作的原子性,CAS远非JAVA代码能实现的功能(下面我们会看到CAS的汇编代码)。
/**
* 假设这段代码是原子性的,那么CAS其实就是这样一个过程
*/
public boolean compareAndSwap(int v,int a,int b) {
if (v == a) {
v = b;
return true;
}else {
return false;
}
}
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
这段话的意思是,CAS操作可以防止内存中共享变量出现脏读脏写问题,多核的CPU在多线程的情况下经常出现的问题,通常我们采用锁来避免这个问题,但是CAS操作避免了多线程的竞争锁,上下文切换和进程调度。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
2.JAVA中的CAS操作实现原理
CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。
以Unsafe类的compareAndSwapInt()方法为例来说,compareAndSwapInt就是借助C语言和汇编代码来实现的。
下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。
下面是JDK中sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:
// native方法,是没有其Java代码实现的,而是需要依靠JDK和JVM的实现
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
可以看到这是个本地方法。这个本地方法在openjdk中依次调用的c++代码为:unsafe.cpp,atomic.cpp和atomicwindowsx86.inline.hpp。这个本地方法的最终实现在openjdk的如下位置:openjdk-7-fcs-src-b147-27jun2011\openjdk\hotspot\src\oscpu\windowsx86\vm\ atomicwindowsx86.inline.hpp(对应于windows操作系统,X86指令集)。下面是对应于intel x86处理器的源代码的片段:
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
__asm je L0 \
__asm _emit 0xF0 \
__asm L0:
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value)
{
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp) // 这里需要先进行判断是否为多核处理器
cmpxchg dword ptr [edx], ecx // 如果是多核处理器就会在这行指令前加Lock标记
}
}
2019.8.15补充:好的~我又回来了,不得不说写这篇文章当时属实浅尝辄止,现在需要解释一下这段汇编代码
int mp = os::is_MP();
os::is_MP()会返回当前JVM运行所在机器是否为多核CPU,当然返回1代表true,0代表false
然后是一段内嵌汇编,C/C++支持内嵌汇编,大家知道这个特性就好,我来通俗易懂的解释一下这段汇编的大体意思。
__asm {
mov edx, dest # 取Atomic::cmpxchg方法的参数dest内存地址存入寄存器edx
mov ecx, exchange_value # 取Atomic::cmpxchg方法的参数exchange_value内存地址存入寄存器ecx
mov eax, compare_value # 取Atomic::cmpxchg方法的参数compare_value内存地存入寄存器eax
LOCK_IF_MP(mp) # 如果是多核处理器,就在下一行汇编代码前加上lock前缀
cmpxchg dword ptr [edx], ecx # 比较ecx和eax的中内存地址的中存的变量值,如果相等就写入edx内存地址中,否则不
}
x86汇编指令cmpxchg本身保证了原子性,其实就是cpu的CAS操作的实现,那么问题来了,为什么保证了原子性还需要在多核处理器中加上lock前缀呢?
答案是:多核处理器中不能保证可见性,lock前缀可以保证这行汇编中所有值的可见性,这个问题的原因是多核CPU中缓存导致的(x86中罪魁祸首是store buffer的存在)。
这样通过lock前缀保障多核处理器的可见性,然后通过cmpxchg指令完成CPU上原子性的CAS操作,完美解决问题!
多说一句,这只是x86中的实现方式,对于其他平台,还是有不同的方式实现,这点希望读者一定要搞清楚。
这段汇编代码看不懂也没关系,但其大意是使用CPU的锁机制,确保了整个CAS操作的原子性。关于CPU中的锁机制和CPU的原子操作 ——CPU中的原子操作。
3.concurrent包中CAS的应用
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
- A线程写volatile变量,随后B线程读这个volatile变量。
- A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
注:volatile 关键字保证了变量的可见性,根据JAVA内存模型,每一个线程都有自己的栈内存,不同线程的栈内存里的变量有可能因为栈内的操作而不同,而 CPU又是直接操作栈中的数据并保存在自己的缓存中,所以多核CPU就出现了很大的问题,而volatile修饰的变量,保证了CPU各个核心不会从栈内存中和 缓存中读数据,而是直接从堆内存中读数据,而且写操作会直接写回堆内存中,从而保证了多线程间共享变量的可见性和局部顺序性(但不保证原子性),关于volatile——Java并发编程:volatile关键字解析
Java的CAS操作可以实现现代CPU上硬件级别的原子指令(不是依靠JVM或者操作系统的锁机制),而同时volatile关键字又保证了线程间共享变量的可见性和指令的顺序性,因此凭借这两种手段,就可以实现不依靠操作系统实现的锁机制来保证并发时共享变量的一致性。
如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
- 首先,声明共享变量为volatile;
- 然后,使用CAS的原子条件更新来实现线程之间的同步;
- 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
4.小结
其实本来还有更多的基础要讲一讲,但是这一篇博客不能太长了,关于JVM内存结构,JMM模型,还有volatile关键字和Java中原生的的同步锁.,这些以后希望能补全,当然也是我自己再次学习的过程记录下来。
参考资料:
Java并发--Java中的CAS操作和实现原理的更多相关文章
- java高并发系列 - 第21天:java中的CAS操作,java并发的基石
这是java高并发系列第21篇文章. 本文主要内容 从网站计数器实现中一步步引出CAS操作 介绍java中的CAS及CAS可能存在的问题 悲观锁和乐观锁的一些介绍及数据库乐观锁的一个常见示例 使用ja ...
- Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理
Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...
- Java并发编程中的若干核心技术,向高手进阶!
来源:http://www.jianshu.com/p/5f499f8212e7 引言 本文试图从一个更高的视角来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在 ...
- Java并发编程中的相关注解
引自:http://www.cnblogs.com/phoebus0501/archive/2011/02/21/1960077.html Java并发编程中,用到了一些专门为并发编程准备的 Anno ...
- Java并发编程中的设计模式解析(二)一个单例的七种写法
Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...
- Go并发编程之美-CAS操作
摘要: 一.前言 go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁.CAS.原子变量操作类.相比Java来说go提供了独特的基于通道的同步措施.本节我 ...
- Java并发编程(七)ConcurrentLinkedQueue的实现原理和源码分析
相关文章 Java并发编程(一)线程定义.状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 Java并发编程(四)Java内存模型 Java并发编程(五)Concurr ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- Java并发--Java线程面试题 Top 50
原文链接:http://www.importnew.com/12773.html 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Ja ...
随机推荐
- python,adb,分别给多个设备安装多个apk文件,os.popen(); os.system; os.path.splitext(); a.split(' \t'); readlines(); append(); os.path.join(); time.sleep();
#encoding:utf-8import os,time#=======================查找手机设备序列号=============a='adb devices'b=os.popen ...
- zabbix使用钉钉告警
1.钉钉创建群 2.[root@localhost ~]# vim /etc/zabbix/zabbix_server.conf # 配置文件中查找”Alert”查看告警脚本存放路径 [root@lo ...
- 《深度学习》圣经"花书"经验法则中文版!
作者:Jeff Macaluso https://jeffmacaluso.github.io/post/DeepLearningRulesOfThumb/ 转自CVer,仅用作个人学习 当我在研究生 ...
- keras 学习笔记(一) ——— model.fit & model.fit_generator
from keras.preprocessing.image import load_img, img_to_array a = load_img('1.jpg') b = img_to_array( ...
- 浅谈字符串Hash
浅谈字符串Hash 本篇随笔讲解Hash(散列表)的一个重要应用:字符串Hash. 关于Hash Hash是一种数据结构,叫做Hash表(哈希表),也叫散列表.关于Hash的实现,其实与离散化颇为类似 ...
- 剑指offer:二叉树打印成多行(层次遍历)
1. 题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行. 2. 思路 层次遍历 3. 递归 public class Solution { ArrayList<Array ...
- QQ音乐2019客户端-获取任意歌单完整歌曲列表和下载音乐文件方法
步骤 1.在web网站上搜搜任意歌单 https://y.qq.com/#type=index/ 例如:中国好声音4.5.6.7.8季 打开后显示网址: https://y.qq.com/n/yqq ...
- Worker Services的新项目模板
.NET Core3.0创建Worker Services2019-10-24 09:05 成天 阅读(1438) 评论(20) 编辑收藏 .NET CORE 3.0新增了Worker Ser ...
- C# 流介绍 (原发布 csdn 2017-09-15 23:37:52)
1.FileStream FileStream 详细介绍参考msdn 写数据: using (FileStream fs = new FileStream("File.FileStream& ...
- aspx.designer.cs没有自动生成代码(没有自动注册)
遇到这个问题的最大可能是:aspx页面存在bug. 比如说我的主页是从项目里的别的页面复制过来的,但是少复制了一些引用,页面就存在bug,导致aspx.designer.cs没有自动生成代码. 解决方 ...