「JUC并发编程」初识CAS锁(概述、底层原理、原子引用、自旋锁、缺点)

这篇具有很好参考价值的文章主要介绍了「JUC并发编程」初识CAS锁(概述、底层原理、原子引用、自旋锁、缺点)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、什么是CAS锁

概述

CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU进行比较两个值是否相等,然后原子地更新某个位置的值。经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。 简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。

原理

CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。

当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来 。

cas锁,JUC并发编程,jvm,java,算法

硬件级别保证

CAS是JDK提供的非阻塞原子性操作,它通过 硬件保证了比较-更新的原子性。

它是非阻塞的且自身原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠

示例代码

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        //期望时5,如果是5则改成2022
        System.out.println(atomicInteger.compareAndSet(5, 2022) + "\t" + atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2022) + "\t" + atomicInteger.get());
    }
}

输出结果

cas锁,JUC并发编程,jvm,java,算法

由于前面修改了,后面修改失败,故先true后false。

源码分析compareAndSet(int expect,int update)

compareAndSet()方法的源代码:

cas锁,JUC并发编程,jvm,java,算法

上面三个方法都是类似的,主要对4个参数做一下说明。

var1:表示要操作的对象

var2:表示要操作对象中属性地址的偏移量

var4:表示需要修改数据的期望的值

var5/var6:表示需要修改为的新值 cas锁,JUC并发编程,jvm,java,算法

引出来一个问题:UnSafe类是什么?

二、CAS底层原理

Unsafe

unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。 Unsafe类存在于sun.misc包中 ,其内部方法操作可以像C的指针 一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

valueOffset

变量valueOffset,表示该变量值在内存中的 偏移地址 ,因为Unsafe就是根据内存偏移地址获取数据的

volatile

变量value用volatile修饰,保证了多线程之间的内存可见性。

源码分析

OpenJDK源码里面查看下 Unsafe.java

cas锁,JUC并发编程,jvm,java,算法

假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上):

1 AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。

2 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。

3 线程B也通过getIntVolatile(var1, var2)方法获取到value值3,此时刚好线程B 没有被挂起 并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。

4 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值已经被其它线程抢先一步修改过了,那A线程本次修改失败, 只能重新读取重新来一遍了。

5 线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

底层汇编

native修饰的方法代表是底层方法

Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于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在内存中的地址,根据偏移量valueOffset,计算 value 的地址 
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); 
// 调用 Atomic 中的函数 cmpxchg来进行比较交换,其中参数x是即将更新的值,参数e是原内存的值 
  return (jint) (Atomic::cmpxchg(x, addr, e)) == e; 
UNSAFE_END 

(Atomic::cmpxchg(x, addr, e)) == e; (主要源码)

cmpxchg

调用 Atomic 中的函数 cmpxchg来进行比较交换,其中参数x是即将更新的值,参数e是原内存的值

return (jint) (Atomic::cmpxchg(x, addr, e)) == e;

unsigned 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); 
} 

总结

你只需要记住:

  • CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
  • 实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令。

核心思想就是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来。

三、原子引用

在上面我们知道AtomicInteger原子整型,那可否有其它原子类型呢?

比如说:AtomicBook、AtomicOrder

答案是肯定的。这里引入AtomicReference

AtomicReference示例

@Data
@AllArgsConstructor
class User{
    String username;
    int age;
}
public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User z3 = new User("z3", 24);
        User li4 = new User("li4", 26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3, li4) + "\t" + atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3, li4) + "\t" + atomicReferenceUser.get().toString());
    }
}

四、自旋锁,借鉴CAS思想

什么是自旋锁?

自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式 去尝试获取锁 ,

当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU 。

示例

题目:实现一个自旋锁
自旋锁好处:循环比较获取没有类似 wait 的阻塞。
通过 CAS 操作完成自旋锁。

public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t" + "---come in");
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t" + "---task over , unlock ...");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(() -> {
            spinLockDemo.lock();
            try {
                TimeUnit.MILLISECONDS.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            spinLockDemo.unlock();
        }, "A").start();

        new Thread(() -> {
            spinLockDemo.lock();

            spinLockDemo.unlock();
        }, "B").start();

    }
}

输出结果

A线程先进,B线程后进。紧接着A线程等待,然后解锁,B线程在A线程解锁后才会解锁。

cas锁,JUC并发编程,jvm,java,算法

解析

A 线程先进来调用 myLock 方法自己持有锁 5 秒钟, B 随后进来后发现
当前有线程持有锁,不是 null ,所以只能通过自旋等待,直到 A 释放锁后 B 随后抢到。

这种自旋等待尝试的过程就是自旋锁。

五、CAS的缺点

循环时间长开销很大

我们可以看到getAndAddInt方法执行时,有个do while 。

cas锁,JUC并发编程,jvm,java,算法

如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

引出来ABA问题

ABA问题怎么产生的

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,

然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。

尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。文章来源地址https://www.toymoban.com/news/detail-594923.html

到了这里,关于「JUC并发编程」初识CAS锁(概述、底层原理、原子引用、自旋锁、缺点)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 并发编程-JUC-原子类

    JUC 整体概览 原子类 基本类型-使用原子的方式更新基本类型 AtomicInteger:整形原子类 AtomicLong:长整型原子类 AtomicBoolean :布尔型原子类 引用类型 AtomicReference:引用类型原子类 AtomicStampedReference:原子更新引用类型里的字段原子类 AtomicMarkableReference :原子更新带有标记位的引

    2024年02月21日
    浏览(40)
  • JUC并发编程之原子类

    目录 1. 什么是原子操作 1.1 原子类的作用 1.2 原子类的常见操作 原子类的使用注意事项 并发编程是现代计算机应用中不可或缺的一部分,而在并发编程中,处理共享资源的并发访问是一个重要的问题。为了避免多线程访问共享资源时出现竞态条件(Race Condition)等问题,J

    2024年02月13日
    浏览(50)
  • JUC并发编程学习笔记(十九)原子引用

    带版本号的原子操作! 解决ABA问题,引入原子引用(乐观锁思想) AtomicStampedReference类解决ABA问题 所有相同类型的包装类对象之间值的比较全部使用equals方法比较 Integer使用了对象缓存机制,默认范围是-128至127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为v

    2024年02月05日
    浏览(56)
  • 【Java 并发编程】CAS 原理解析

    悲观锁 的原理是每次实现数据库的增删改的时候都进⾏阻塞,防⽌数据发⽣脏读。 乐观锁 的原理是在数据库更新的时候,⽤⼀个 version 字段来记录版本号,然后通过⽐较是不是⾃⼰要修改的版本号再进⾏修改。这其中就引出了⼀种⽐较交换的思路来实现数据的⼀致性,事实

    2024年02月06日
    浏览(38)
  • JUC并发编程之AQS原理

    全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架 特点: 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个生态,控制如何获取锁和释放锁 getState - 获取 state 状态 setState - 设置 state 状态 compareAndSetState - cas 机制设置 s

    2023年04月18日
    浏览(86)
  • JUC并发编程原理精讲(源码分析)

    JUC即 java.util.concurrent 涉及三个包: java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks 普通的线程代码: Thread Runnable 没有返回值、效率相比入 Callable 相对较低! Callable 有返回值!【工作常用】 进程 :是指一个内存中运行的程序,每个进程都有一个独立的内存空间,

    2024年02月02日
    浏览(45)
  • 【并发编程】JUC并发编程(彻底搞懂JUC)

    如果你对多线程没什么了解,那么从入门模块开始。 如果你已经入门了多线程(知道基础的线程创建、死锁、synchronized、lock等),那么从juc模块开始。 新手学技术、老手学体系,高手学格局。 JUC实际上就是我们对于jdk中java.util .concurrent 工具包的简称 ,其结构如下: 这个包

    2024年02月20日
    浏览(51)
  • Java并发编程挑战与解决方案:上下文切换、死锁、资源限制及底层实现原理

    深入探讨Java并发编程中的挑战,包括上下文切换、死锁、资源限制,并介绍解决方案如减少上下文切换、避免死锁等。了解Java并发机制的底层实现原理和线程间通信方法。

    2024年02月01日
    浏览(45)
  • 【并发编程】CAS到底是什么

    Java实现CAS的原理 | Java程序员进阶之路 美团终面:CAS确定完全不需要锁吗? CAS 是 Compare-And-Swap (比较并交换)的缩写,是一种 轻量级的同步机制 ,主要用于实现多线程环境下的无锁算法和数据结构,保证了并发安全性。它可以在 不使用锁 (如synchronized、Lock)的情况下,对

    2024年02月20日
    浏览(41)
  • 第九章 JUC并发编程

    http://t.csdn.cn/UgzQi 使用 AQS加 Lock 接口实现简单的不可重入锁 早期程序员会自己通过一种同步器去实现另一种相近的同步器,例如用可重入锁去实现信号量,或反之。这显然不够优雅,于是在 JSR166(java 规范提案)中创建了 AQS,提供了这种通用的同步器机制。 AQS 要实现的功能

    2023年04月08日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包