当前位置: 首页 > 编程笔记 >

Java CAS底层实现原理实例详解

鲁昕
2023-03-14
本文向大家介绍Java CAS底层实现原理实例详解,包括了Java CAS底层实现原理实例详解的使用技巧和注意事项,需要的朋友参考一下

这篇文章主要介绍了Java CAS底层实现原理实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

一、CAS(compareAndSwap)的概念

CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。

CAS(V, A, B),V为内存地址、A为预期原值,B为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 CAS 指令之前返回该位置的值。而我们可以使用自旋锁,循环CAS,重新读取该变量再尝试再次修改该变量,也可以放弃操作。

二、CAS(compareAndSwap)的产生

为什么需要CAS机制呢?我们先从一个错误现象谈起。我们经常使用volatile关键字修饰某一个变量,表明这个变量是全局共享的一个变量,同时具有了可见性和有序性。但是却没有原子性。比如说一个常见的操作a++。这个操作其实可以细分成三个步骤:

(1)从内存中读取a

(2)对a进行加1操作

(3)将a的值重新写入内存中

在单线程状态下这个操作没有一点问题,但是在多线程中就会出现各种各样的问题了。因为可能一个线程对a进行了加1操作,还没来得及写入内存,其他的线程就读取了旧值。造成了线程的不安全现象。

Volatile关键字可以保证线程间对于共享变量的可见性可有序性,可以防止CPU的指令重排序(DCL单例),但是无法保证操作的原子性,所以jdk1.5之后引入CAS利用CPU原语保证线程操作的院子性。

CAS操作由处理器提供支持,是一种原语。原语是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。如 Intel 处理器,比较并交换通过指令的 cmpxchg 系列实现。

三、CAS(compareAndSwap)的原理探究

CAS的实现主要在JUC中的atomic包,我们以AtomicInteger类为例:

通过代码追溯,可以看出JAVA中的CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,由JVM本地实现,所以最终的实现是基于C、C++在操作系统之上操作

Unsafe类,在sun.misc包下,不属于Java标准。Unsafe类提供一系列增加Java语言能力的操作,如内存管理、操作类/对象/变量、多线程同步等

//var1为CAS操作的对象,offset为var1某个属性的地址偏移值,expected为期望值,var2为要设置的值,利用JNI来完成CPU指令的操作
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
public native Object getObjectVolatile(Object var1, long var2);
public native void putObjectVolatile(Object var1, long var2, Object var4);
Hotspot源码中关于unsafe的实现hotspot\src\share\vm\prims\unsafe.cpp
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);根据偏移量,计算value的地址。这里的offset就是 AtomaicInteger中的valueOffset
 jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
 return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END\hotspot\src\share\vm\runtime\atomic.cppunsigned Atomic::cmpxchg(unsigned int exchange_value,
             volatile unsigned int* dest, unsigned int compare_value) {
 assert(sizeof(unsigned int) == sizeof(jint), "more work to do");
 return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest,
                    (jint)compare_value);
}根据操作系统类型调用不同平台下的重载函数,这个在预编译期间编译器会决定调用哪个平台下的重载

可以看到调用了“Atomic::cmpxchg”方法,“Atomic::cmpxchg”方法在linux_x86和windows_x86的实现如下

linux_x86底层实现\hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp
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;
}
windows_x86底层实现
hotspot\src\os_cpu\windows_x86\vmatomic_linux_x86.inline.hpp
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
 }
}

总结:根据资料查询,其实CAS底层实现根据不同的操作系统会有不同重载,CAS的实现离不开处理器的支持。

核心代码就是一条带lock 前缀的 cmpxchg 指令,即lock cmpxchg dword ptr [edx], ecx

Atomic::cmpxchg方法解析:

mp是“os::is_MP()”的返回结果,“os::is_MP()”是一个内联函数,用来判断当前系统是否为多处理器。

如果当前系统是多处理器,该函数返回1。

否则,返回0。

LOCK_IF_MP(mp)会根据mp的值来决定是否为cmpxchg指令添加lock前缀。

如果通过mp判断当前系统是多处理器(即mp值为1),则为cmpxchg指令添加lock前缀。

否则,不加lock前缀。

这是一种优化手段,认为单处理器的环境没有必要添加lock前缀,只有在多核情况下才会添加lock前缀,因为lock会导致性能下降。cmpxchg是汇编指令,作用是比较并交换操作数。

四、CAS机制的优缺点

4.1 优点

cas是一种乐观锁,而且是一种非阻塞的轻量级的乐观锁,什么是非阻塞式的呢?其实就是一个线程想要获得锁,对方会给一个回应表示这个锁能不能获得。在资源竞争不激烈的情况下性能高,相比synchronized重量锁,synchronized会进行比较复杂的加锁,解锁和唤醒操作。

4.2 缺点

1)循环时间长开销大,占用CPU资源

2)只能保证一个共享变量的原子操作

3)ABA问题

4.3 解决ABA问题

1)添加版本号

2)AtomicStampedReference

java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

五、CAS使用的时机

5.1 线程数较少、等待时间短可以采用自旋锁进行CAS尝试拿锁,较于synchronized高效

5.2 线程数较大、等待时间长,不建议使用自旋锁,占用CPU较高

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces)、控制组(Control groups)、Union 文件系统(Union file systems)和容器格式(Container format)。 我们知道,传统的虚拟机通过在宿主主机中运行 hypervisor 来模拟一整套完整的硬件环境提供给虚拟机的操作系统。虚拟机系统看到的环境是可限制的,也是彼此隔离的。这

  • 1、基本架构 Docker 采用了 C/S架构,包括客户端和服务端。 Docker daemon 作为服务端接受来自客户的请求,并处理这些请求(创建、运行、分发容器)。 客户端和服务端既可以运行在一个机器上,也可通过 socket 或者 RESTful API 来进行通信。 Docker daemon 一般在宿主主机后台运行,等待接收来自客户端的消息。 Docker 客户端则为用户提供一系列可执行

  • 本文向大家介绍Python底层封装实现方法详解,包括了Python底层封装实现方法详解的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了Python底层封装实现方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 事实上,python封装特性的实现纯属“投机取巧”,之所以类对象无法直接调用私有方法和属性,是因为底层实现时,python

  • 本文向大家介绍ReentrantLock实现原理详解,包括了ReentrantLock实现原理详解的使用技巧和注意事项,需要的朋友参考一下 以下是本篇文章的大纲 1 synchronized和lock     1.1 synchronized的局限性     1.2 Lock简介 2 AQS 3 lock()与unlock()实现原理     3.1 基础知识     3.2 内部结构     3

  • 本文向大家介绍通过实例了解Python异常处理机制底层实现,包括了通过实例了解Python异常处理机制底层实现的使用技巧和注意事项,需要的朋友参考一下 要了解try except异常处理的用法,简单来说,当位于 try 块中的程序执行出现异常时,会将该种异常捕获,同时找到对应的 except 块处理该异常,那么这里就有一个问题,它是如何找到对应的 except 块的呢? 我们知道,一个 try 块

  • 主要内容:1 索引的数据结构,2 B-Tree索引的介绍,2.1 为什么选择B-Tree结构,2.2 B+Tree和B-Tree结构的区别,3 MyISAM的BTREE索引实现,4 InnoDB的BTREE索引实现,4.1 ROWID,5 B-Tree索引的有效查询类型深入解析了Mysql的B+Tree索引底层数据结构,以及MyISAM和InnoDB 存储引擎的索引底层原理。 下面我们来看看常见的索引结构的底层实现原理。包括B-Tree、B+Tree的数据结构,以及MyISAM和InnoDB 存