别忘记奔跑-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问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/youhaitao_do/article/details/111876375
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/602617

相关文章

MySQL启动报错:InnoDB表空间丢失问题及解决方法

《MySQL启动报错:InnoDB表空间丢失问题及解决方法》在启动MySQL时,遇到了InnoDB:Tablespace5975wasnotfound,该错误表明MySQL在启动过程中无法找到指定的s... 目录mysql 启动报错:InnoDB 表空间丢失问题及解决方法错误分析解决方案1. 启用 inno

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

电脑蓝牙连不上怎么办? 5 招教你轻松修复Mac蓝牙连接问题的技巧

《电脑蓝牙连不上怎么办?5招教你轻松修复Mac蓝牙连接问题的技巧》蓝牙连接问题是一些Mac用户经常遇到的常见问题之一,在本文章中,我们将提供一些有用的提示和技巧,帮助您解决可能出现的蓝牙连接问... 蓝牙作为一种流行的无线技术,已经成为我们连接各种设备的重要工具。在 MAC 上,你可以根据自己的需求,轻松地

Java 中的跨域问题解决方法

《Java中的跨域问题解决方法》跨域问题本质上是浏览器的一种安全机制,与Java本身无关,但Java后端开发者需要理解其来源以便正确解决,下面给大家介绍Java中的跨域问题解决方法,感兴趣的朋友一起... 目录1、Java 中跨域问题的来源1.1. 浏览器同源策略(Same-Origin Policy)1.

如何清理MySQL中的binlog问题

《如何清理MySQL中的binlog问题》:本文主要介绍清理MySQL中的binlog问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目http://www.chinasem.cn录清理mysql中的binlog1.查看binlog过期时间2. 修改binlog过期

如何解决yum无法安装epel-release的问题

《如何解决yum无法安装epel-release的问题》:本文主要介绍如何解决yum无法安装epel-release的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录yum无法安装epel-release尝试了第一种方法第二种方法(我就是用这种方法解决的)总结yum

IDEA下"File is read-only"可能原因分析及"找不到或无法加载主类"的问题

《IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题》:本文主要介绍IDEA下Fileisread-only可能原因分析及找不到或无法加载主类的问题,具有很好的参... 目录1.File is read-only”可能原因2.“找不到或无法加载主类”问题的解决总结1.File

idea中project的显示问题及解决

《idea中project的显示问题及解决》:本文主要介绍idea中project的显示问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录idea中project的显示问题清除配置重China编程新生成配置总结idea中project的显示问题新建空的pr

redis在spring boot中异常退出的问题解决方案

《redis在springboot中异常退出的问题解决方案》:本文主要介绍redis在springboot中异常退出的问题解决方案,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴... 目录问题:解决 问题根源️ 解决方案1. 异步处理 + 提前ACK(关键步骤)2. 调整Redis消费者组

Ubuntu上手动安装Go环境并解决“可执行文件格式错误”问题

《Ubuntu上手动安装Go环境并解决“可执行文件格式错误”问题》:本文主要介绍Ubuntu上手动安装Go环境并解决“可执行文件格式错误”问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未... 目录一、前言二、系统架构检测三、卸载旧版 Go四、下载并安装正确版本五、配置环境变量六、验证安装七、常见