【并发编程】-3.锁的类型、CAS、UNSAFE、原子操作

锁的类型

可重入锁和不可重入锁

  1. 可重入锁:一个线程可以多次抢占同一个锁;SynchronizedReentrantLock都是可重入锁,用Synchronized进行锁的可重入测试,在同一个线程中定义childMethod()childMethod2()两个方法,在这两个方法中都使用Synchronized修饰,代码可以正常运行;

    package com.xrl.juc.synchronized_demo;/*** synchronized 可重入实验** @version [v1.0]* @author: [xrl]* @create: [2024/06/28 14:43]**/
    public class Child extends Parent {public synchronized void childMethod() {System.out.println("Child method");// 调用另一个加锁方法,嵌套调用childMethod2();// 在子类中调用父类的同步方法parentMethod();}public synchronized void childMethod2() {System.out.println("Child method2");// 在子类中调用父类的同步方法parentMethod();}public static void main(String[] args) {Child child = new Child();child.childMethod();}
    }class Parent {public synchronized void parentMethod() {System.out.println("Parent method");}
    }
    
  2. 不可重入锁:不可递归调用,递归调用就发生死锁,自旋锁一般情况下是不可重入的,但可在自旋加锁前加入判断可改为可重入的;

    package com.xrl.juc.cas;import java.util.Objects;
    import java.util.concurrent.atomic.AtomicReference;/*** @version [v1.0]* @author: [xrl]* @create: [2024/06/28 14:59]**/
    public class UnReentrantDemo {SimpleSpinLock lock = new SimpleSpinLock();public void set() {try {lock.lock();System.out.println("set 方法");//在同一个线程中先在set方法中获得锁 , 调用get方法 又在get方法中获得同一个锁,因为不可重入所以会进入死循环,不会输出get 方法get();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock(); //释放锁}}public void get() {try {lock.lock();System.out.println("get 方法");} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {UnReentrantDemo lockTest = new UnReentrantDemo();lockTest.set();}
    }
    class SimpleSpinLock {private final AtomicReference<Thread> owner = new AtomicReference<>();private volatile boolean locked = false;private volatile int count = 0;public void lock() {Thread currentThread = Thread.currentThread();// 可重入判断 
    //        if (owner.get() == currentThread) {
    //            // 当前线程已经持有锁,增加计数器
    //            count++;
    //            return;
    //        }// 自旋尝试获取锁while (!locked || Objects.nonNull(owner.get())) {// 如果锁没有被占用或者被当前线程占用,尝试设置ownerif (!locked && owner.compareAndSet(null, currentThread)) {// 设置成功,获取锁locked = true;return;}}}public void unlock() {Thread currentThread = Thread.currentThread();//  可重入判断
    //        if (count > 0) {
    //            count--; 
    //            return;
    //        }if (owner.get() == currentThread) {// 释放锁locked = false;// 清除ownerowner.set(null);} else {throw new IllegalMonitorStateException("Calling thread has not locked this lock");}}
    }
    

公平锁和非公平锁

  1. 公平锁:先到先得,获得锁的顺序遵循先进先出FIFO原则,先对锁进行获取的请求,一定先满足 ;

    1. ReentrantLock可以是公平锁也可以是非公平锁,在创建时可以指定;public ReentrantLock(boolean fair);//sync = fair ? new FairSync() : new NonfairSync();

      两者之前的区别在于FairSync使用lock()方法获取锁之前,会先去判断同步队列中是否存在元素(hasQueuedPredecessors()方法),如果存在则将当前任务放入同步队列的队尾;

      NonfairSync只判断当前锁是否有人占用,如果没有就直接获取;

  2. 非公平锁:不按加锁顺序执行,非公平锁的吞吐量大于公平锁,是主流操作系统线程调度的基本选择;

    1. synchronized是非公平锁:原因是synchronized是重量级锁, 它的线程间的调度和状态变更由操作系统负责,在线程切换A线程到B线程的时候要进行上下文切换需要耗费一段时间,在这个时间间隔上锁是空闲的如果此时有另一个线程C在cpu的另一个核心上执行,就不需要进行上下文切换,这时C就可以获得锁,这样利用了锁的空档期,提高了cpu利用效率,但是可能会导致“线程饥饿 ”即某个线程一直没有获得到锁。

自旋锁

  1. 概念:当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,在循环中不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环;
  2. 简单实现:参考不可重入锁的例子;

悲观锁和乐观锁

  1. 悲观锁:悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改,悲观地认为,不加锁的并发操作一定会出问题;因此对于并发操作,悲观锁都会采取加锁的方式来保证线程安全,synchronizedReentrantLock都属于悲观锁;
  2. 乐观锁:乐观锁认为在大多数情况下,多个用户对同一数据的修改操作是不会冲突的,因此不需要加锁限制,只是在修改数据时,通过版本比较判断数据是否被其他线程操作,如果一致则说明数据没被其他线程修改可以进行更新操作,不一致则说明数据被被其他线程操作过,不能进行更新操作,需要回滚事务或重新尝试;
    1. 实现:一般通过版本号和CAS实现,JAVA中的原子操作类就是采用这一思想实现的,也可通过在mysql表中添加Version字段来实现乐观锁;

共享锁和独占锁

  1. 共享锁:指可以同时被多个线程获取的锁;
    1. 实现Java 中的ReentrantReadWriteLock(读写锁)中的读锁、Mysql读锁等;
  2. 独占锁:指任何时候都只有一个线程能执行资源操作;
    1. 实现Java 中的ReentrantReadWriteLock(读写锁)中的写锁、Mysql写锁、synchronizedReentrantLock等;

可中断锁和不可中断锁

  1. 中断锁:指锁在执行时可被中断,也就是在执行时可以接收interrupt的通知,从而中断锁执行;
    1. 实现ReentrantLock是可中断锁;中断锁使用方法是lock.lockInterruptibly()方法,它和lock.lock()方法作用类似,只不过lockInterruptibly()方法可以优先接收中断的请求;
  2. 不可中断锁:指锁在执行时不可被中断;
    1. 实现synchronized是不可中断锁;

CAS(Compare And Swap)

  1. 概念:它核心就是比较并交换;CAS算法是一种无锁算法,乐观锁的一种实现;

  2. 原理:CAS有3个操作数,内存所存值V,旧的预期值A,要修改的新值B;当且仅当预期值A和内存值V相同时将内存值V修改为B并返回true,否则返回false

  3. 操作系统的支持:它的最终实现是依赖于CPU原子性指令实现,也就是判断值是否相等、更新值等多个步骤是一个整体,执行必须连续,执行过程中也不可被中断;

  4. ABA问题:线程T1读取变量值为 A;线程T2将变量值更改为 B; 之后T2又将变量值更改为 A; 这时T1线程去更新,发现此时变量值与之前读到的值是相同的,就以为没有被其它线程更新过,但实际已经被变更过;--------------------,案例参考文章Unsafe类中CAS相关的代码案例

    1. 解决办法:加入一个时间戳或者递增变量等实现的版本号,来标识这个变量是否之前被更新过;
  5. CAS无锁自旋存在的问题

    1. 自旋时间长:如果自旋CAS长时间不成功,就会占用大量的CPU资源;

      解决办法:破坏掉for死循环,当超过一定时间或者一定次数时,退出循环;

    2. 内存顺序冲突Memory Order Violation):当持有锁的线程快要释放锁的时,一般情况下会执行一个store命令,而等待获取锁的线程此时在不断自旋,会不断发出load命令,而此处并没任何happen-before 规则限制,此时就可能出现持有锁线程store命令尚未完成,而其他线程任然在不断执行lode操作,这可能导致程序出现异常,为了避免出出现这种情况,处理器需要进行流水线清空并重排序;

      解决办法:在自旋等待时插入Pause指令,pause指令能让自旋失败时cpu稍微停顿一下再继续自旋,从而使load命令执行频率低一些;

      • 流水线清空并重排序:当处理器检测到有依赖于未完成操作结果的后续指令时,它会选择清空流水线并重排序来保证内存操作顺序一致性的强制性;(当执行读取操作时,要确保所有先前的写入操作都已经完成并且对其他线程可见);

UNSAFE类:

  1. 概念:Java中的Unsafe类为我们提供了类似C++直接访问、操作内存的能力,这也就意味Unsafe可以操作不受JVM管理的内存,也就代表这无法被GC需要如C++一样手动释放,稍有不慎就会出现内存泄漏;

  2. 功能:主要提供了如下几大块能力,更详细的API能力可以参考官方文档;

    在这里插入图片描述

  3. Unsafe对象的获取Unsafe功能虽然强大,但对于JAVA程序而言它的操作是不安全的,所以我们无法直接通过方法获取到Unsafe,需要通过反射获取;

    package com.xrl.juc.unsafe_demo;
    import sun.misc.Unsafe;
    import java.lang.reflect.Field;public class UnsafeUtil {public static Unsafe get() {try {/***直接获取会抛出异常java.lang.SecurityException* 我们写的程序调用的是AppClassLoader加载器,Unsafe类Botstarp类价值器加载,* 在返回时回去判断是否是系统类加载器VM.isSystemDomainLoader(caller.getClassLoader())*/// Unsafe unsafe = Unsafe.getUnsafe();/*** 通过反射获取到Unsafe类中的 private static final Unsafe theUnsafe;这个属性*  usafe在静态代码块中已经  theUnsafe = new Unsafe(); 创建了这个属性*/Field field = Unsafe.class.getDeclaredField("theUnsafe");//将属性设置为可取field.setAccessible(true);//获得属性值return (Unsafe) field.get(null);} catch (NoSuchFieldException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}return null;}
    }
    
  4. CAS相关:

    //参数说明:   要操作的对象      要操作属性的偏移量  预期值  要更新的值
    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);
    
  5. 代码案例:通过一个案例来体会一下通过Unsafe实现CAS修改属性值

    package com.xrl.juc.unsafe_demo;import sun.misc.Unsafe;public class MyUnsafe {//要设置的值public volatile int state = 0;public static void main(String[] args) throws NoSuchFieldException {MyUnsafe myUnsafe = new MyUnsafe();//获得Unsafe实例Unsafe unsafe = UnsafeUtil.get();//获得要修改属性的偏移量long stateOffset = unsafe.objectFieldOffset(MyUnsafe.class.getDeclaredField("state"));//通过偏移量来修改state的值            要操作的对象      要操作属性的偏移量  预期值  要更新的值boolean b = unsafe.compareAndSwapInt(myUnsafe, stateOffset, 0, 5);System.out.println("是否修改成功" + b);System.out.println("更新后的值" + myUnsafe.state);/*** 使用unsafe.compareAndSwapInt(myUnsafe, stateOffset, 0, 3)* 只有本身的值与预期值相同时才可修改成功**/boolean v = unsafe.compareAndSwapInt(myUnsafe, stateOffset, 5, 3);System.out.println("是否修改成功" + v);System.out.println("再次更新后的值" + myUnsafe.state);}
    }
    
  6. UNSAFEJDK版本中的变更:

    1. JDK9计划移除:JEP 260
      1. 提供jdk.internal.misc.Unsafe功能更加强大,使用jdk.internal.misc.Unsafe需要添加启动参数--add-opens java.base/jdk.internal.misc=ALL-UNNAMED --illegal-access=warn
      2. 发布VarHandle着手替代Unsafe中部分功能;
    2. 预计JDK23弃用内存访问方法:JDK 23 ;通过VarHandle API(JEP 193,JDK 9) Foreign Function & Memory API(JEP 454,JDK 22)来替换;

原子操作类

  1. 概念JAVAJDK5推出的JUC并发包中提供了java.util.concurrent.atomic原子包,在该包下提供了各种原子操作类,内部主要通过UNSAFECAS实现的原子操作;

  2. 原子类的分类:具体的API使用可参考官方文档;

    1. 基本数据类型的原子类AtomicInteger、AtomicBoolean、AtomicLong

    2. 引用类型原子类AtomicReference、AtomicStampedReference、AtomicMarkableReference

    3. 数组类型原子类AtomicIntegerArray、AtomicLongArray、AtomicLongArray

    4. 属性类型原子类AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

    5. 累加器LongAdder、DoubleAdder

    6. 积累器LongAccumulator、DoubleAccumulator

  3. 使用限制

    1. 操作的字段不能被static、final修饰;
    2. 操作的字段需要被volatile修饰,从而保证数据的可见性;
    3. 属性的访问权限范围必须包含当前的操作区域;
  4. 自定义原子类:通过一个自定义原子类来体会一下原子类的实现原理,主要是依靠UNSAFE得到操作字段偏移量,从而获得到属性的旧值,之后通过自旋+CAS来设置值,并通过volatile保证操作字段的可见性;

    package com.xrl.juc.atomic_demo;import com.xrl.juc.unsafe_demo.UnsafeUtil;
    import sun.misc.Unsafe;
    /*** 自定义原子操作类* @author xrl* @create 2024-06-30 17:04*/
    public class MyAtomicLong {private static Unsafe unsafe;private static long valueOffset;private volatile long value;static {unsafe = UnsafeUtil.get();try {// 通过属性名获取字段偏移量valueOffset = unsafe.objectFieldOffset(MyAtomicLong.class.getDeclaredField("value"));} catch (NoSuchFieldException e) {e.printStackTrace();}}public MyAtomicLong(long value) {this.value = value;}public final long incrementAndGet() {//自旋long v;do {v = unsafe.getLongVolatile(this, valueOffset);System.out.println(Thread.currentThread().getName() + "值为" + v);} while (!unsafe.compareAndSwapLong(this, valueOffset, v, v + 1L));return v;}public long get() {return unsafe.getLong(this, valueOffset);}public static void main(String[] args) {MyAtomicLong aLong = new MyAtomicLong(5);System.out.println(aLong.incrementAndGet());System.out.println(aLong.get());}
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/41601.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Github Actions自建Docker镜像仓库

使用Github Actions自建Docker镜像仓库 背景使用Github Actions自建Docker镜像仓库fork项目[docker_image_sync](https://github.com/xqxyxchy/docker_image_sync)获取云厂商容器镜像服务信息配置github secrets运行github action配置需要同步的镜像同步后效果华为云配置 背景 …

VCS+Vivado联合仿真BUG

场景&#xff1a; 在vcsvivado联合仿真过程中&#xff0c;对vivado导出的shell脚本修改&#xff0c;修改某些source文件路径&#xff0c;vcs编译时会报Permission Denied。 问题描述 对shell脚本修改如下&#xff1a; 修改仅为注释掉某一行&#xff0c;下面变为source文件新…

昇思25天学习打卡营第07天|函数式自动微分

神经网络的训练主要使用反向传播算法&#xff0c;模型预测值&#xff08;logits&#xff09;与正确标签&#xff08;label&#xff09;送入损失函数&#xff08;loss function&#xff09;获得loss&#xff0c;然后进行反向传播计算&#xff0c;求得梯度&#xff08;gradients&…

hid-ft260驱动学习笔记 1 - 驱动模块注册与注销

目录 1. ft260_driver_init初始化 1.1 tty设备 1.1.1 申请tty驱动设备 1.1.2 初始化tty驱动程序 1.1.3 注册tty设备 1.2 hid设备 2. ft260_driver_exit注销模块 3. 调试 hid-ft260.c的最底部可以看到该驱动的注册与注销接口的申明。 module_init(ft260_driver_init); …

eclipse ide中文件编码的修改,解决中文乱码的问题。

1、先上一张图&#xff1a; 记得之前设置过&#xff0c;但是稍微一变&#xff0c;环境编码又到了ISO-8859-1了&#xff0c;然后就出现了乱码。 2、设置eclipse的编码&#xff1a; Preferences--General -- Content Types -- Text -- Java Properties File -- Default encoding…

使用myCobot280和OAK-D OpenCV DepthAI摄像头制作一个实时脸部跟踪的手机支架!

引言 由于YouTube和Netflix的出现&#xff0c;我们开始躺着看手机。然而&#xff0c;长时间用手拿着手机会让人感到疲劳。这次我们制作了一个可以在你眼前保持适当距离并调整位置的自动移动手机支架&#xff0c;让你无需用手拿着手机。请务必试试&#xff01; 准备工作 这次我们…

Vue3从入门到精通(三)

vue3插槽Slots 在 Vue3 中&#xff0c;插槽&#xff08;Slots&#xff09;的使用方式与 Vue2 中基本相同&#xff0c;但有一些细微的差异。以下是在 Vue3 中使用插槽的示例&#xff1a; // ChildComponent.vue <template><div><h2>Child Component</h2&…

昇思25天学习打卡营第08天|模型训练

模型训练 模型训练一般分为四个步骤&#xff1a; 构建数据集。定义神经网络模型。定义超参、损失函数及优化器。输入数据集进行训练与评估。 现在我们有了数据集和模型后&#xff0c;可以进行模型的训练与评估。 ps&#xff1a;这里的训练和Stable Diffusion中的训练是一样…

进程的概念

一.进程和程序的理解 首先抛出结论&#xff1a;进程是动态的&#xff0c;暂时存在于内存中&#xff0c;进程是程序的一次执行&#xff0c;而进程总是对应至少一个特定的程序。 程序是静态的&#xff0c;永久的存在于磁盘中。 程序是什么呢&#xff1f;程序其实就是存放在我们…

图像分类-数据驱动方法

K近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09; KNN算法通过比较新样本与训练集中的样本的距离&#xff0c;然后根据最近的K个邻居的投票结果来决定新样本的分类。 如图所示&#xff0c;K越大的边界会更加平滑&#xff0c;本质上是根据某一样本最近…

红薯小眼睛接口分析与Python脚本实现

文章目录 1. 写在前面2. 接口分析3. 算法脚本实现 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Py…

递归(三)—— 初识暴力递归之“字符串的全部子序列”

题目1 &#xff1a; 打印一个字符串的全部子序列 题目分析&#xff1a; 解法1&#xff1a;非递归方法 我们通过一个实例来理解题意&#xff0c;假设字符串str “abc”&#xff0c;那么它的子序列都有那些呢&#xff1f;" ", “a”&#xff0c; “b”&#xff0c;…

Vue的民族民俗文化分享平台-计算机毕业设计源码22552

基于Vue的民族民俗文化分享平台设计与实现 摘 要 本文介绍了一种基于Vue.js前端框架和Express后端框架的民族民俗文化分享平台的设计和实现。该平台旨在通过线上方式&#xff0c;促进民族民俗文化的传播与分享&#xff0c;增强公众对多元文化的了解和认同。 平台为普通用户提供…

前后端的导入、导出、模板下载等写法

导入&#xff0c;导出、模板下载等的前后端写法 文章目录 导入&#xff0c;导出、模板下载等的前后端写法一、导入实现1.1 后端的导入1.2 前端的导入 二、基础的模板下载2.1 后端的模板下载-若依基础版本2.2 前端的模板下载2.3 后端的模板下载 - 基于资源文件读取2.4 excel制作…

24西安电子科技大学马克思主义学院—考研录取情况

01、马克思主义学院各个方向 02、24马克思主义学院近三年复试分数线对比 PS&#xff1a;马院24年院线相对于23年院线增加15分&#xff0c;反映了大家对于马克思主义理论学习与研究的热情高涨&#xff0c;也彰显了学院在人才培养、学科建设及学术研究等方面的不断进步与成就。 6…

直接更新flowable数据库的流程定义信息的一种方法

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a; h…

文章解读与仿真程序复现思路——太阳能学报EI\CSCD\北大核心《绿电交易场景下计及温控负荷的高铁站两阶段调度策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

【第21章】MyBatis-Plus多数据源支持

文章目录 前言一、dynamic-datasource1. 特性2. 约定3. 使用方法3.1 引入依赖3.2 配置数据源3.3 使用 DS 切换数据源 二、mybatis-mate1.特性2.使用方法2.1 配置数据源2.2 使用 Sharding 切换数据源2.3 切换指定数据库节点 三、实战1. 引入库2. 配置3. 使用 DS 切换数据源4. 测…

vue项目打包部署后 浏览器自动清除缓存问题(解决方法)

vue打包部署后 浏览器缓存问题&#xff0c;导致控制台报错ChunkLoadError: Loading chunk failed的解决方案 一、报错如下&#xff1a; 每次build打包部署到服务器上时&#xff0c;偶尔会出现前端资源文件不能及时更新到最新&#xff0c;浏览器存在缓存问题&#xff0c;这时在…

Pandas数据可视化详解:大案例解析(第27天)

系列文章目录 Pandas数据可视化解决不显示中文和负号问题matplotlib数据可视化seaborn数据可视化pyecharts数据可视化优衣库数据分析案例 文章目录 系列文章目录前言1. Pandas数据可视化1.1 案例解析&#xff1a;代码实现 2. 解决不显示中文和负号问题3. matplotlib数据可视化…