当前位置:网站首页>JUC(二)原子类:CAS、乐观锁、Unsafe和原子类
JUC(二)原子类:CAS、乐观锁、Unsafe和原子类
2022-08-02 05:03:00 【学到的心态】
本文了解下CAS、乐观锁、Unsafe和原子类相关知识。
文章目录
一. CAS
1. CAS介绍
cas全称是compare and swap 。是一条CPU的原子指令,其作用是让CPU先比较两个旧值是否相等,然后原子性的更新某个位置的值。
其实现方式是基于硬件平台的汇编指令,也就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,而AtomicInteger便是使用了这些封装的接口。
2. 使用
不使用CAS,在高并发下,多线程同时修改一个变量的值,我们需要synchronized加锁。
不过我们可以使用AtomicInteger 原子类(底层基于CAS进行更新数据的),在不加锁的情况下,实现并发下数据的一致性。
public class Test {
private AtomicInteger i = new AtomicInteger(0);
public int add(){
return i.addAndGet(1);
}
}
3. CAS的问题
3.1. ABA
假设一个值从A到B又更新成了A,此时值发生了变化,但是CAS检查时却是没有变化。
ABA问题的解决思路就是使用版本号:在变量前面加上版本号,每次更新时把版本号 +1 。
从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决 ABA 问题。
类的compareAndSet方法的作用先检查值和标志是否符合预期,如果全相等则将值和标志更新为指定的值。
3.2. 循环时间长开销大
自旋(循环执行CAS指令)CAS如果长时间不成功,会给CPU带来非常大的执行开销。
如果JVM能支持处理器提供的pause指令,那么效率会有一定的提升。pause指令有两个作用:
- 第一,它可以延迟流水线执行命令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零;
- 第二,它可以避免在退出循环的时候因内存顺序冲突(Memory Order Violation)而引起CPU流水线被清空(CPU Pipeline Flush),从而提高CPU的执行效率。
3.3. 只能保证一个共享变量的原子操作
CAS只能对使一个变量保证原子性,如果是多个共享变量操作,则需要锁。
从Java 1.5开始,JDK提供了AtomicReference类来保证引用对象之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。
二. UnSafe类详解
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但同时也意味着不安全。
这个类尽管里面的方法都是 public 的,但是并没有办法使用它们,JDK API 文档也没有提供任何关于这个类的方法的解释。总而言之,对于 Unsafe 类的使用都是受限制的,只有授信的代码才能获得该类的实例,当然 JDK 库里面的类是可以随意使用的。
UnSafe类总体功能:
Unsafe 和 CAS
//反编译出来的代码:
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
i = getIntVolatile(paramObject, paramLong);
while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}
public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
{
Object localObject;
do
localObject = getObjectVolatile(paramObject1, paramLong);
while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
return localObject;
}
从反编译的代码可以知道:一个线程对一个值更新(使用incrementAndGet)时,如果更新不成功则继续循环更新,知道更新成功为止。 这称为自旋。
我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong。都是native方法。
Unsafe底层
再看看Unsafe的compareAndSwap*方法来实现CAS操作,它是一个本地方法,实现位于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);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
可以看到它通过 Atomic::cmpxchg 来实现比较和替换操作。其中参数x是即将更新的值,参数e是原内存的值。
三. AtomicInteger底层逻辑
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
// 使用 AtomicInteger 后,不需要加锁,也可以实现线程安全
public int getCount() {
return count.get();
}
public class AtomicInteger extends Number implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//用于获取value字段相对当前对象的“起始地址”的偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex); }
}
private volatile int value;
//返回当前值
public final int get() {
return value;
}
//递增加detla
public final int getAndAdd(int delta) {
//三个参数,1、当前的实例 2、value实例变量的偏移量 3、当前value要加上的数(value+delta)。
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//递增加1
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
...
}
可以看到 AtomicInteger 底层用的是volatile的变量和CAS来进行更改数据的。
- volatile保证线程的可见性,多线程并发时,一个线程修改数据,可以保证其它线程立马看到修改后的值
- CAS 保证数据更新的原子性(保证一个线程要么执行成功,要么就自旋,不放弃CPU)。
四. AtomicReference
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args){
// 创建两个Person对象,它们的id分别是101和102。
Person p1 = new Person(101);
Person p2 = new Person(102);
// 新建AtomicReference对象,初始化它的值为p1对象
AtomicReference ar = new AtomicReference(p1);
// 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
ar.compareAndSet(p1, p2);
Person p3 = (Person)ar.get();
System.out.println("p3 is "+p3);
System.out.println("p3.equals(p1)="+p3.equals(p1));
}
}
class Person {
volatile long id;
public Person(long id) {
this.id = id;
}
public String toString() {
return "id:"+id;
}
}
//p3 is id:102
//p3.equals(p1)=false
五. AtomicStampedReference解决ABA问题
AtomicStampedReference主要维护包含一个对象引用以及一个可以自动更新的整数"stamp"的pair对象来解决ABA问题。
public class AtomicStampedReference<V> {
private static class Pair<T> {
final T reference; //维护对象引用
final int stamp; //用于标志版本
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
private volatile Pair<V> pair;
....
/** * expectedReference :更新之前的原始值 * newReference : 将要更新的新值 * expectedStamp : 更新之前的标志版本 * newStamp : 将要更新的标志版本 */
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) {
// 获取当前的(元素值,版本号)对
Pair<V> current = pair;
return
// 首先要求 引用和版本号 都符合更新之前的
expectedReference == current.reference && expectedStamp == current.stamp &&
// 然后判断:1. 没有更新直接返回
((newReference == current.reference && newStamp == current.stamp) ||
// 2. 更新操作:先构建新的Pair对象,然后再将CAS更新
casPair(current, Pair.of(newReference, newStamp)));
}
private boolean casPair(Pair<V> cmp, Pair<V> val) {
// 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
看一个CAS失败的例子:
private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);
public static void main(String[] args) {
Thread main = new Thread(() -> {
System.out.println("操作线程" + Thread.currentThread() + ",初始值 Reference = " + atomicStampedRef.getReference());
int stamp = atomicStampedRef.getStamp();
try {
Thread.sleep(1000); //等待1秒 ,以便让干扰线程执行,导致expectedReference 不等于1。
} catch (InterruptedException e) {
e.printStackTrace();
}
//此时expectedReference未发生改变(1->2->1),但是stamp已经被修改了,所以CAS失败
boolean isCASSuccess = atomicStampedRef.compareAndSet(1, 2, stamp, stamp + 1);
System.out.println("操作线程" + Thread.currentThread() + ",CAS操作结果: " + isCASSuccess + ", Reference=" + atomicStampedRef.getReference());
}, "主操作线程");
Thread other = new Thread(() -> {
Thread.yield(); // 放弃执行 确保thread-main 优先执行
atomicStampedRef.compareAndSet(1, 2, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
System.out.println("操作线程" + Thread.currentThread() + ",【increment】 ,Reference = " + atomicStampedRef.getReference());
atomicStampedRef.compareAndSet(2, 1, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
System.out.println("操作线程" + Thread.currentThread() + ",【decrement】 ,Reference = " + atomicStampedRef.getReference());
}, "干扰线程");
main.start();
other.start();
}
操作线程Thread[主操作线程,5,main],初始值 Reference = 1
操作线程Thread[干扰线程,5,main],【increment】 ,Reference = 2
操作线程Thread[干扰线程,5,main],【decrement】 ,Reference = 1
//如果是AtomicInteger 则返回ture
//boolean isCASSuccess = count.compareAndSet(1, 2);
操作线程Thread[主操作线程,5,main],CAS操作结果: false, Reference=1
java中还有哪些类可以解决ABA的问题?
AtomicMarkableReference,它不是维护一个版本号,而是维护一个boolean类型的标记,标记值有修改,了解一下。
参考:
https://pdai.tech/md/java/thread/java-thread-x-juc-AtomicInteger.html
边栏推荐
猜你喜欢
[email protected](using passwordYES)"/>Navicat报错:1045-Access denied for user [email protected](using passwordYES)

matlab simulink 模糊pid结合smith控制温度

ELK日志分析系统

MySql copies data from one table to another table

Detailed explanation of AMQP protocol

【Gazebo入门教程】第一讲 Gazebo的安装、UI界面、SDF文件介绍

Mycat2.0搭建教程

navicat新建数据库

MySQL 5.7 detailed download, installation and configuration tutorial

认识CAN光纤转换器的光纤接口和配套光纤线缆
随机推荐
MySQL 8.0.29 设置和修改默认密码
浏览器的onload事件
interrupt()、interrupted()和isInterrupted()你真的懂了吗
MySQL导入sql文件的三种方法
MYSQL 唯一约束
What do interview test engineers usually ask?The test supervisor tells you
面试测试工程师一般会问什么?测试主管告诉你
2022河南萌新联赛第(四)场:郑州轻工业大学 C - 最大公因数
Navicat报错:1045-Access denied for user [email protected](using passwordYES)
Mysql子查询关键字的使用(exists)
ES6——class类实现继承
MySQL 8.0.28 version installation and configuration method graphic tutorial
数学建模学习笔记:层次分析法(AHP)
[QNX Hypervisor 2.2用户手册]9.17 tolerance
Matlab paper illustration drawing template No. 41 - bubble chart (bubblechart)
H5接入支付流程-微信支付&支付宝支付
MySQL 5.7升级到8.0详细过程
c语言:查漏补缺(三)
MySQL 5.7 upgrade to 8.0 detailed process
【MLT】MLT多媒体框架生产消费架构解析(一)