别忘记奔跑-volatile CAS ABA问题

2024-01-13 19:59

本文主要是介绍别忘记奔跑-volatile CAS ABA问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、对volatile的理解

1.JMM

2.volatile

3.你在哪些地方用到过volatile?

二、CAS你知道吗?

2.1 比较并交换

2.2 CAS底层原理 对UnSafe的理解

2.2.1 atomicInteger.getAndIncrement();

2.2.2 Unsafe

2.2.3 CAS是什么?Unsafe类+CAS思想(自旋)

2.3 CAS缺点

三、原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?

3.1 ABA问题是怎么产生的?

3.2 原子引用

3.3 时间戳原子引用


一、对volatile的理解

1.JMM

1.1可见性、原子性、VolatileDemo代码演示可见性+原子性代码、有序性 

1.2 JMM 线程安全性获得保证

工作内存与主内存同步延迟现象导致的可见性问题:可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见

对于指令重排导致的可见性问题和有序性问题:可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

2.volatile

是Java虚拟机提供的轻量级的同步机制(乞丐版的synchronized):保证可见性,不保证原子性,禁止指令重排

代码演示:


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;class MyData{//MyData.java ===>MyData.class ===>jvm 字节码volatile int number = 0;public void addT060(){this.number=60;}//请注意,此时number前面是加了volatile关键字修饰的,volatile不保证原子性public void addPlusPlus(){number++;}AtomicInteger atomicInteger = new AtomicInteger();public void addAtomic(){atomicInteger.getAndIncrement();}
}/*** 1 验证volatile 的可见性*  1.1 假如int number = 0;number变量之前根本没有添加volatile关键字修饰,没有可见性*  1.2 添加了volatile,可以解决可见性问题** 2 验证volatile不保证原子性*  2.1 原子性指的是什么意思?*      不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整*      要么同时成功,要么同时失败*  2.2 volatile 不保证原子性的案例演示*  2.3 why? 数值少于20000 出现了丢失写值的情况(写覆盖)要写的时候有可能有的线程被挂起了*  2.4 如何解决原子性?*      * 加sync*      * 使用我们的juc下AtomicInteger*/public class VolatileDemo {public static void main(String[] args) { //main是一切方法的运行入口MyData myData = new MyData();for (int i = 1; i <= 20; i++) {new Thread(()->{for (int j=1;j<=1000;j++){myData.addPlusPlus();myData.addAtomic();}}, String.valueOf(i)).start();}//需要等待上面20个线程全部计算完成后,再用main线程取得最终的结果值看是多少?//暂停一会线程while (Thread.activeCount()>2){Thread.yield();}System.out.println(Thread.currentThread().getName()+"\t int type, finally number value:"+myData.number);//volatile如果保证原子性的话,这个值应该是2W,而执行结果却不是(某次为19468)System.out.println(Thread.currentThread().getName()+"\t AtomicInteger type, finally number value:"+myData.atomicInteger);}public static void seeOkByVolatile() {MyData myData = new MyData();new Thread(()-> {System.out.println(Thread.currentThread().getName() + "\t come in");//暂停一会线程try {TimeUnit.SECONDS.sleep(3);//3秒钟之后执行将number加到60,可是这时候main线程还在那等着,不知道!} catch (InterruptedException e) {e.printStackTrace();}myData.addT060();System.out.println(Thread.currentThread().getName() + "\t updated number value:" + myData.number);},"AAA").start();//第2个线程就是我们的main线程while (myData.number==0){//main线程一直再这里循环等待,直到number值不再等于零}System.out.println(Thread.currentThread().getName() + "\t mission is over");}}

Java 汇编案例解释:

禁止指令重排小总结:

3.你在哪些地方用到过volatile?

3.1 单例模式DCL代码


public class SingletonDemo {private static SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo()");}//DCL (Double Check Lock 双端检锁机制)//以下这种写法,99.99%的正确性,底层有可能出现指令重排public static SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class){if (instance==null){instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {//单线程(main线程的操作动作.................)
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println();
//        System.out.println();
//        System.out.println();//并发多线程后,情况发生了很大的变化for (int i = 1; i <= 10; i++) {new Thread(() -> {SingletonDemo.getInstance();}, String.valueOf(i)).start();}}
}

3.2 单例模式volatile分析

但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。

所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

在instance前面加上volatile,禁止指令重排

public class SingletonDemo {private static volatile SingletonDemo instance = null;private SingletonDemo() {System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingletonDemo()");}//DCL (Double Check Lock 双端检锁机制)//以下这种写法,99.99%的正确性,底层有可能出现指令重排public static SingletonDemo getInstance() {if (instance == null) {synchronized (SingletonDemo.class){if (instance==null){instance = new SingletonDemo();}}}return instance;}public static void main(String[] args) {//单线程(main线程的操作动作.................)
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println();
//        System.out.println();
//        System.out.println();//并发多线程后,情况发生了很大的变化for (int i = 1; i <= 10; i++) {new Thread(() -> {SingletonDemo.getInstance();}, String.valueOf(i)).start();}}
}

二、CAS你知道吗?

2.1 比较并交换

如果线程的期望值和物理内存的真实值一样,我就修改我的更新值,返回true 进行修改。

2.2 CAS底层原理 对UnSafe的理解

2.2.1 atomicInteger.getAndIncrement();

2.2.2 Unsafe

CAS是比较并交换,它保证原子性靠的是底层的Unsafe类

2.2.3 CAS是什么?Unsafe类+CAS思想(自旋)

2.2.3.1 unsafe.getAndAddInt 

CAS全称为Compare-And-Swap,它是一条CPU并发原语

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

//unsafe.getAndAddInt

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);//获取当前对象var1在var2地址上的值是多少,赋值给var5} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//比较并交换,比较快照值和物理内存真实值是否相等return var5;
}

2.2.3.2 底层汇编

2.2.3.3 简单版小总结

CAS(CompareAndSwap)

比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。

CAS应用

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

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

2.3 CAS缺点

1)循环时间长开销很大。

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

    

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

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

    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

3)引出来ABA问题(重点)

三、原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?

CAS -----> Unsafe ------> CAS底层思想 -------> ABA --------> 原子引用更新 ----------> 如何规避ABA问题

* ABA :狸猫换太子

* 解决ABA问题??? 理解原子引用+新增一种机制,那就是修改版本号(类似时间戳)

  CAS不够???

3.1 ABA问题是怎么产生的?

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

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。

尽管线程one的CAS操作成功,但是并不代表这个过程就是没有问题的。

3.2 原子引用


import java.util.concurrent.atomic.AtomicReference;class User{String userName;int age;public User(String userName, int age) {this.userName = userName;this.age = age;}@Overridepublic String toString() {return "User{" +"userName='" + userName + '\'' +", age=" + age +'}';}
}public class AtomicReferenceDemo {public static void main(String[] args) {User z3 = new User("z3",22);User li4 = new User("li4", 25);AtomicReference<User> atomicReference = new AtomicReference<>();atomicReference.set(z3);System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());}
}

3.3 时间戳原子引用

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;public class ABADemo { //ABA问题的解决 AtomicStampedReference --->带时间戳的原子引用static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);public static void main(String[] args) {System.out.println("===============以下是ABA问题的产生====================");new Thread(()->{atomicReference.compareAndSet(100,101);atomicReference.compareAndSet(101,100);},"t1").start(); //t1干了一次ABAnew Thread(()->{try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());},"t2").start();//暂停一会儿线程try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ e.printStackTrace(); }System.out.println("===============以下是ABA问题的解决====================");new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);//暂停1秒钟t3线程try { TimeUnit.SECONDS.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); }atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println(Thread.currentThread().getName() + "\t第2次版本号:" + atomicStampedReference.getStamp());atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);System.out.println(Thread.currentThread().getName() + "\t第3次版本号:" + atomicStampedReference.getStamp());},"t3").start();new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);//暂停3秒钟t4线程,保证上面的t3线程完成了一次ABA操作try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }boolean res = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);System.out.println(Thread.currentThread().getName() + "\t修改成功否" + res+"\t当前最新实际版本号:"+atomicStampedReference.getStamp());System.out.println(Thread.currentThread().getName() + "\t当前实际最新值:"+atomicStampedReference.getReference());},"t4").start();}
}

执行结果如下:

 

 

 

这篇关于别忘记奔跑-volatile CAS ABA问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/602617

相关文章

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

idea npm install很慢问题及解决(nodejs)

《ideanpminstall很慢问题及解决(nodejs)》npm安装速度慢可通过配置国内镜像源(如淘宝)、清理缓存及切换工具解决,建议设置全局镜像(npmconfigsetregistryht... 目录idea npm install很慢(nodejs)配置国内镜像源清理缓存总结idea npm in

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

idea突然报错Malformed \uxxxx encoding问题及解决

《idea突然报错Malformeduxxxxencoding问题及解决》Maven项目在切换Git分支时报错,提示project元素为描述符根元素,解决方法:删除Maven仓库中的resolv... 目www.chinasem.cn录问题解决方式总结问题idea 上的 maven China编程项目突然报错,是

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造

Python绘制TSP、VRP问题求解结果图全过程

《Python绘制TSP、VRP问题求解结果图全过程》本文介绍用Python绘制TSP和VRP问题的静态与动态结果图,静态图展示路径,动态图通过matplotlib.animation模块实现动画效果... 目录一、静态图二、动态图总结【代码】python绘制TSP、VRP问题求解结果图(包含静态图与动态图

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

k8s容器放开锁内存限制问题

《k8s容器放开锁内存限制问题》nccl-test容器运行mpirun时因NCCL_BUFFSIZE过大导致OOM,需通过修改docker服务配置文件,将LimitMEMLOCK设为infinity并... 目录问题问题确认放开容器max locked memory限制总结参考:https://Access

Java中字符编码问题的解决方法详解

《Java中字符编码问题的解决方法详解》在日常Java开发中,字符编码问题是一个非常常见却又特别容易踩坑的地方,这篇文章就带你一步一步看清楚字符编码的来龙去脉,并结合可运行的代码,看看如何在Java项... 目录前言背景:为什么会出现编码问题常见场景分析控制台输出乱码文件读写乱码数据库存取乱码解决方案统一使