Java中提供了原子操作,可以简单看一下AtomicInteger类中的一个典型的原子操作incrementAndGet(),表示对原子整数变量进行加操作,并返回新的值。实现如下:

public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset; static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
} private volatile int value; public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}

在实现incrementAndGet()操作时,由于后续要执行CAS(compare and swap,比较并交换)操作,这个操作需要对旧值与某个地址处的值进行比较,但是在Java层无法操作地址,所以只能计算出某个字段在当前类实例中的偏移,然后在HotSpot VM中根据偏移转换为对应的地址。

调用的getAndAddInt()方法的实现如下:

public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}

其中的compareAndSwapInt()是native方法,对应的实现如下: 

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

调用的Atomic::cmpxchg()函数的实现如下:

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

inline jint Atomic::cmpxchg(jint exchange_value, volatile jint *dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}

如上在C++函数中内联了一段汇编程序。使用精练的汇编不但可以缩小目标代码的大小,还可以使用汇编来提高某些经常被卧调用的代码的性能。

内联汇编的基本格式如下:

__asm__ [__volatile__] (
assembler template // 汇编代码模板
: [output operand list] // 输出操作数列表
: [input operand list] // 输入操作数列表
: [clobbered register list] // 修改的寄存器列表
);

内联汇编可以将C++函数中相关信息通过输入操作数列表传送到汇编指令中,也可以通过输出操作数列表接收到由汇编指令执行后的输出值。下面详细介绍所一下Atomic::cmpxchg()函数中内联汇编的具体意思。

1、汇编代码模板:当操作系统为多核时,mp为true,此时会在cmpxhgl指令之前加一个lock前缀。因为cmpxhgl指令本身并不是原子的(cmpxhg解码为多个微指令,这些微指令加载、检查是否相等,然后根据比较结果存储或不存储新值),但是加lock前缀后就会变为原子的。cmpxhg的操作数可以是reg + reg,也可以是mem + reg,前者不需要lock,因为在同一个核上,寄存器只会有一套。只有cmpxhg mem, reg才可能会需要lock,这个lock是对多核有效的。使用的cmpxhgl指令有个后缀l,表示操作数是4字节大小。

2、输出操作数列表,=表示操作数在指令中是只写的(输出操作数),a表示将变量放入eax寄存器。在64位模式下,只有%rax可用,因为在执行内联汇编相关的指令时之前会自动保存%rax的值,这样避免重要数据丢失。

3、r表示将输入变量放入通用寄存器,也就是eax,ebx,ecx,edx,esi,edi中的一个。a同样表示eax寄存器。%1就是exchange_value,%3dest,%4就是mp。

4、在修改的寄存器列表中,cc表示编译器汇编代码会导致CPU状态位的改变,也就是eflags指示了CPU状态。这里由于执行cmpxhgl,所以会更改eflags的状态;memory告诉编译器汇编代码会读取或修改内存中某个地址存放的值。

在HotSpot的atomic.hpp中声明了许多原子操作,这些操作不但为Java层原子操作提供实现,也会在HotSpot内部经常使用。主要是因为CAS相对互斥量来说更加轻量级,效率更高,但是达到同样的目的时,实现也相对复杂了一些。下面就举几个小例子,如下:

1、CAS保证在多线程竞争下,通过指针碰撞分配TLAB 

在分配TLAB时会通过CAS来保证并发安全。实际上采用CAS配合上失败重试的方式保证更新操作的原子性,如下:

inline HeapWord* ContiguousSpace::par_allocate_impl(
size_t size,
HeapWord* const end_value
) {
do {
HeapWord* obj = top();
// 当前的空闲空间足够分配时尝试分配
if (pointer_delta(end_value, obj) >= size) {
HeapWord* new_top = obj + size;
HeapWord* result = (HeapWord*)Atomic::cmpxchg_ptr(new_top, top_addr(), obj);
if (result == obj) {
return obj; // 分配成功时返回,否则继续循环
}
} else {
return NULL; // 没有足够空间时候返回
}
} while (true);
}

2、保证一个或多个共享变量的原子操作

首先说一下,CAS只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java 1.5 开始,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行 CAS 操作。在HotSpot VM实现轻量级锁时,也会有类似的操作。MarkWord将多个变量拼接为了一个64位数,如下:

在偏向锁的实现过程中,需要同时判断thread、epoch及biased_lock值来确定接下来的逻辑时,就将这几个数看成了一个64位的数进行了原子操作。

3、CAS实现自旋等待

在HotSpot VM内部锁Monitor的实现过程中,使用CAS进行自旋等待,以避免上下文切换。在Monitor::ILock()函数中,如果产生锁竞争,当前线程会调用Monitor::TrySpin ()进行自旋等待。这里等待时间的选取非常关键,因为如果自旋时间长则浪费CPU时间,旋转短了又不能有效避免上下文切换。其中的等待时间与Marsaglia的xor-shift算法产生的伪随机数有直接关系,有兴趣的可自行研究。

4、原子更新变量保护代码段线程安全

多线程竞争时,可以保护一段代码同一时刻只有一个线程在执行。在Monitor中有一个volatile变量,如下:

ParkEvent * volatile _OnDeck

这个变量被HotSpot VM作者标注为内部锁,也就是借助它可实现一段代码保护。

当执行一段代码时,可以通过_OnDeck将NULL设置为_LBIT,在退出时将_OnDeck再次设置为_LBIT,这样其它的CAS就又可以执行这段被保护的代码了。如下:

void Monitor::IUnlock (bool RelaxAssert) {
... // 获取内部锁
if (CASPTR (&_OnDeck, NULL, _LBIT) != UNS(NULL)) {
return ;
} // 确保同一时只有一个线程在执行这里的代码 // 释放内部锁
_OnDeck = NULL ; }

CAS操作无处不在,只要用的好、用的巧,还是能极大减少互斥量的使用的。 

手写Java虚拟机HotSpot已经录制一系列视频啦!有兴趣关注B站

有对虚拟机、Java性能故障诊断与调优等感兴趣的人可以入群讨论。

 

  

关于CAS等原子操作,说点别人没说的的更多相关文章

  1. CAS、原子操作类的应用与浅析及Java8对其的优化

    前几天刷朋友圈的时候,看到一段话:如果现在我是傻逼,那么我现在不管怎么努力,也还是傻逼,因为我现在的傻逼是由以前决定的,现在努力,是为了让以后的自己不再傻逼.话糙理不糙,如果妄想现在努力一下,马上就不 ...

  2. java并发编程系列二:原子操作/CAS

    什么是原子操作 不可被中断的一个或者一系列操作 实现原子操作的方式 Java可以通过锁和循环CAS的方式实现原子操作 CAS( Compare And Swap )  为什么要有CAS? Compar ...

  3. CAS原子操作实现无锁及性能分析

    CAS原子操作实现无锁及性能分析 Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn.net/chen19870707 ...

  4. java原子操作CAS

    本次内容主要讲原子操作的概念.原子操作的实现方式.CAS的使用.原理.3大问题及其解决方案,最后还讲到了JDK中经常使用到的原子操作类. 1.什么是原子操作? 所谓原子操作是指不会被线程调度机制打断的 ...

  5. CAS 原子操作

    理会CAS和CAS: 有时候面试官面试问你的时候,会问,谈谈你对CAS的理解,这时应该有很多人,就会比较懵,当然,我也会比较懵,当然我和很多人的懵不同,很多人可能,并不知道CAS是一个什么东西,而在我 ...

  6. CAS机制总结

    一.简介 CAS机制:(Compare and set)比较和替换 简单来说–>使用一个期望值来和当前变量的值进行比较,如果当前的变量值与我们期望的值相等,就用一个新的值来更新当前变量的值CAS ...

  7. Java并发指南3:并发三大问题与volatile关键字,CAS操作

    本文转载自互联网,侵删   序言 先来看如下这个简单的Java类,该类中并没有使用任何的同步. 01 final class SetCheck { 02 private int  a = 0; 03 ...

  8. JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法

    JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...

  9. 锁&锁与指令原子操作的关系 & cas_Queue

    锁 锁以及信号量对大部分人来说都是非常熟悉的,特别是常用的mutex.锁有很多种,互斥锁,自旋锁,读写锁,顺序锁,等等,这里就只介绍常见到的, 互斥锁 这个是最常用的,win32:CreateMute ...

  10. CAS无锁算法与ConcurrentLinkedQueue

    CAS:Compare and Swap 比较并交换 java.util.concurrent包完全建立在CAS之上的,没有CAS就没有并发包.并发包借助了CAS无锁算法实现了区别于synchroni ...

随机推荐

  1. 基于词袋(Bag of Words)和SVM的图片分类

    目录 摘要 源码及完整报告: 词袋(Bag of Words, BoW) 基于词袋模型的图片分类基本流程 多尺度空间极值点检测 关键点精确定位 关键点主方向计算 生成描述子 特征词典的生成 SVM分类 ...

  2. python中引用自己封装的包飘红线处理办法

    1.安装 opencv-contrib-python  可解决引用自己包名提示 无法识别 2.取消unresolved referencesde 的勾勾

  3. Prometheus-3:一文详解promQL

    读前提示: 本文字数较多且紧凑,最好预留15min一次性看完,好营养,易吸收. promQL详解 Prometheus提供了内置的数据查询语言PromQL(全称为Prometheus Query La ...

  4. Android 自定义view中根据状态修改drawable图片

    原文地址:Android 自定义view中根据状态修改drawable图片 - Stars-One的杂货小窝 本文涉及知识点: Android里的selector图片使用 底部导航栏的使用 自定义vi ...

  5. Codeforces Round #875 (Div. 2) A-D

    比赛链接 A 代码 #include <bits/stdc++.h> using namespace std; using ll = long long; bool solve() { i ...

  6. Cilium系列-5-Cilium替换KubeProxy

    系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, ...

  7. Mysql高级2-SQL性能分析

    一.SQL执行频率 MySQL客户端 连接成功后,通过show [session | global] status 命令可以提供服务器状态信息,通过如下指令,可以查看当前数据库的insert,upda ...

  8. 代码随想录算法训练营第二天| LeetCode 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II

    977.有序数组的平方 题目链接:https://leetcode.cn/problems/squares-of-a-sorted-array/ 文章讲解:https://programmercarl ...

  9. .net core 因路径原因导致的JSON解析错误

    因解析json配置文件导致的错误: JsonReaderException: '0xEF' is an invalid escapable character within a JSON string ...

  10. 部署ELK+filebeat收集nginx日志

    前言 简介 ELK(Elasticsearch.Logstash.Kibana)是开源的实时日志收集分析解决方案. Elasticsearch:开源搜索引擎,是一个基于Lucene.分布式.通过Res ...