什么是 Java 中的 Unsafe 与 CAS ?

参考:https://mp.weixin.qq.com/s/JFLqL1GGW0fFXoGbHxmowAhtml

Unsafe

简单讲一下这个类。Java 没法直接访问底层操做系统,而是经过本地(native)方法来访问。不过尽管如此,JVM 仍是开了一个后门,JDK 中有一个类 Unsafe,它提供了硬件级别的原子操做。java

这个类尽管里面的方法都是 public 的,可是并无办法使用它们,JDK API 文档也没有提供任何关于这个类的方法的解释。总而言之,对于 Unsafe 类的使用都是受限制的,只有授信的代码才能得到该类的实例,固然 JDK 库里面的类是能够随意使用的。算法

从第一行的描述能够了解到 Unsafe 提供了硬件级别的操做,好比说获取某个属性在内存中的位置,好比说修改对象的字段值,即便它是私有的。不过 Java 自己就是为了屏蔽底层的差别,对于通常的开发而言也不多会有这样的需求。数组

举两个例子,比方说:安全

这个方法能够用来获取给定的 paramField 的内存地址偏移量,这个值对于给定的 field 是惟一的且是固定不变的。再好比说:并发

前一个方法是用来获取数组第一个元素的偏移地址,后一个方法是用来获取数组的转换因子即数组中元素的增量地址的。最后看三个方法:atom

分别用来分配内存,扩充内存和释放内存的。操作系统

固然这须要有必定的 C/C++ 基础,对内存分配有必定的了解,这也是为何我一直认为 C/C++ 开发者转行作 Java 会有优点的缘由。线程

CAS

CAS,Compare and Swap 即比较并交换,设计并发算法时经常使用到的一种技术,java.util.concurrent 包全完创建在 CAS 之上,没有 CAS 也就没有此包,可见 CAS 的重要性。设计

当前的处理器基本都支持 CAS,只不过不一样的厂家的实现不同罢了。CAS 有三个操做数:内存值 V、旧的预期值 A、要修改的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改成 B 并返回 true,不然什么都不作并返回 false。

CAS 也是经过 Unsafe 实现的,看下 Unsafe 下的三个方法:

就拿中间这个比较并交换 Int 值为例好了,若是咱们不用 CAS,那么代码大体是这样的:

固然这段代码在并发下是确定有问题的,有可能线程 1 运行到了第 5 行正准备运行第 7 行,线程 2 运行了,把 i 修改成 10,线程切换回去,线程1因为先前已经知足第 5 行的 if 了,因此致使两个线程同时修改了变量 i。

解决办法也很简单,给 compareAndSwapInt 方法加锁同步就好了,这样,compareAndSwapInt 方法就变成了一个原子操做。CAS 也是同样的道理,比较、交换也是一组原子操做,不会被外部打断,先根据 paramLong/paramLong1 获取到内存当中当前的内存值 V,在将内存值 V 和原值 A 做比较,要是相等就修改成要修改的值 B,因为 CAS 都是硬件级别的操做,所以效率会高一些。

由CAS分析AtomicInteger原理

java.util.concurrent.atomic 包下的原子操做类都是基于 CAS 实现的,下面拿 AtomicInteger 分析一下,首先是 AtomicInteger 类变量的定义:

关于这段代码中出现的几个成员属性:

一、Unsafe是 CAS 的核心类,前面已经讲过了。

二、valueOffset 表示的是变量值在内存中的偏移地址,由于 Unsafe 就是根据内存偏移地址获取数据的原值的。

三、value 是用 volatile 修饰的,这是很是关键的。

下面找一个方法 getAndIncrement 来研究一下 AtomicInteger 是如何实现的,好比咱们经常使用的 addAndGet 方法:

这段代码如何在不加锁的状况下经过 CAS 实现线程安全,咱们不妨考虑一下方法的执行:

一、AtomicInteger 里面的 value 原始值为 3,即主内存中 AtomicInteger 的 value 为 3,根据 Java 内存模型,线程 1 和线程 2 各自持有一份 value 的副本,值为 3。

二、线程 1 运行到第三行获取到当前的 value 为 3,线程切换。

三、线程 2 开始运行,获取到 value 为 3,利用 CAS 对比内存中的值也为 3,比较成功,修改内存,此时内存中的 value 改变比方说是 4,线程切换。

四、线程 1 恢复运行,利用 CAS 比较发现本身的 value 为 3,内存中的 value 为 4,获得一个重要的结论 –> 此时 value 正在被另一个线程修改,因此我不能去修改它。

五、线程 1 的 compareAndSet 失败,循环判断,由于 value 是 volatile 修饰的,因此它具有可见性的特性,线程 2 对于 value 的改变能被线程 1 看到,只要线程 1 发现当前获取的 value 是 4,内存中的 value 也是 4,说明线程 2 对于 value 的修改已经完毕而且线程 1 能够尝试去修改它。

六、最后说一点,好比说此时线程 3 也准备修改 value 了,不要紧,由于比较-交换是一个原子操做不可被打断,线程 3 修改了 value,线程 1 进行 compareAndSet 的时候必然返回的 false,这样线程 1 会继续循环去获取最新的 value 并进行 compareAndSet,直至获取的 value 和内存中的 value 一致为止。

整个过程当中,利用 CAS 机制保证了对于 value 的修改的线程安全性。

CAS的缺点

CAS 看起来很美,但这种操做显然没法涵盖并发下的全部场景,而且 CAS 从语义上来讲也不是完美的,存在这样一个逻辑漏洞:若是一个变量 V 初次读取的时候是 A 值,而且在准备赋值的时候检查到它仍然是 A 值,那咱们就能说明它的值没有被其余线程修改过了吗?若是在这段期间它的值曾经被改为了 B,而后又改回 A,那 CAS 操做就会误认为它历来没有被修改过。这个漏洞称为 CAS 操做的 ”ABA” 问题。java.util.concurrent 包为了解决这个问题,提供了一个带有标记的原子引用类 ”AtomicStampedReference”,它能够经过控制变量值的版原本保证 CAS 的正确性。不过目前来讲这个类比较”鸡肋”,大部分状况下 ABA 问题并不会影响程序并发的正确性,若是须要解决 ABA 问题,使用传统的互斥同步可能回避原子类更加高效。


原文连接:

www.cnblogs.com/xrq730/p/4976007.html

相关文章
相关标签/搜索