广告
返回顶部
首页 > 资讯 > 后端开发 > Python >java 多线程与并发之volatile详解分析
  • 714
分享到

java 多线程与并发之volatile详解分析

2024-04-02 19:04:59 714人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录CPU、内存、缓存的关系CPU缓存什么是CPU缓存为什么要有多级CPU CacheJava内存模型(Java Memory Model,JMM)JMM导致的并发安全问题可见性原子

CPU、内存、缓存的关系

要理解JMM,要先从计算机底层开始,下面是一份大佬的研究报告

在这里插入图片描述

计算机在做一些我们平时的基本操作时,需要的响应时间是不一样的!如果我们计算一次a+b所需要的的时间:

  • CPU读取内存获得a,100纳秒
  • CPU读取内存获得b,100纳秒
  • CPU执行一条指令 a+b ,0.6纳秒

也就是说99%的时间花费在CPU读取内存上了,那如何解决速度不均衡问题?

早期计算机中cpu和内存的速度是差不多的,但在现代计算机中cpu的指令速度远超内存的存取速度,由于计算机的存储设备与处理器的运算速度有几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了

CPU缓存

什么是CPU缓存

在计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

下图是一个典型的存储器层次结构,我们可以看到一共使用了三级缓存:

在这里插入图片描述

为什么要有多级CPU Cache

在计算机系统中,寄存器划是L0级缓存,接着依次是L1,L2,L3(接下来是内存,本地磁盘,远程存储)。越往上的缓存存储空间越小,速度越快,成本也更高;越往下的存储空间越大,速度更慢,成本也更低。从上至下,每一层都可以看做是更下一层的缓存,即:L0寄存器是L1一级缓存的缓存,L1是L2的缓存,依次类推;每一层的数据都是来至它的下一层,所以每一层的数据是下一层的数据的子集

在这里插入图片描述


下图是我电脑的三级缓存,可以看到层级越小容量越小。速度越快价格越高!!

在这里插入图片描述

在现代CPU上,一般来说L0, L1,L2,L3都集成在CPU内部,而L1还分为一级数据缓存(Data Cache,D-Cache,L1d)和一级指令缓存(Instruction Cache,I-Cache,L1i),分别用于存放数据和执行数据的指令解码。每个核心拥有独立的运算处理单元、控制器、寄存器、L1、L2缓存,然后一个CPU的多个核心共享最后一层CPU缓存L3。

为了充分利用 CPU Cache,Java提出了内存模型这个概念

Java内存模型(Java Memory Model,JMM)

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化

在这里插入图片描述

程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的工作内存(Local Memory),工作内存中存储了该线程以读/写共享变量的副本。

在这里插入图片描述

举个栗子:多个线程去修改主内存中的变量a。线程不能直接修改主内存中的数据,先把数据拷贝到工作内存,线程对私有的工作内存修改然后再同步到主内存。那这样做会带来什么问题呢?

JMM导致的并发安全问题

从JMM角度看,如果两个线程同时调用 a=a+1这个函数(假设a的初始值是0),A、B线程同时从主内存中拷贝a=0,然后修改写回,最后主内存为a=1,咋搞?

在这里插入图片描述

如下是代码栗子


public class MainTest {
    
    private  long count = 0;

    public  void incCount() {
        count += 1;
    }

    public static void main(String[] args) throws InterruptedException {

        MainTest test = new MainTest();
        Count count = new Count(test);
        Count count1 = new Count(test);
        count.start();
        count1.start();
        Thread.sleep(5);
        System.out.println("result is :" + test.count);
    }
    
    private static class Count extends Thread{
        private MainTest m;

        public Count(MainTest m){
            this.m = m;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                m.incCount();
            }
        }
    }
} 

执行结果

// 第一次执行
> Task :lib-test:MainTest.main()
result is :11861

// 第二次执行
> Task :lib-test:MainTest.main()
result is :10535

可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

由于线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量,那么对于共享变量a,它们首先是在自己的工作内存,之后再同步到主内存。可是并不会及时的刷到主存中,而是会有一定时间差。很明显,这个时候线程 A 对变量 a 的操作对于线程 B 而言就不具备可见性了 。

要解决共享对象可见性这个问题,我们可以使用volatile关键字或者是加

原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行

我们都知道CPU资源的分配都是以线程为单位的,并且是分时调用,操作系统允许某个进程执行一小段时间,例如 50 毫秒,过了 50 毫秒操作系统就会重新选择一个进程来执行(我们称为“任务切换”),这个 50 毫秒称为“时间片”。而任务的切换大多数是在时间片段结束以后。

那么线程切换为什么会带来bug呢?因为操作系统做任务切换,可以发生在任何一条CPU 指令执行完!注意,是 CPU 指令,CPU 指令,CPU 指令,而不是高级语言里的一条语句。比如count++,在java里就是一句话,但高级语言里一条语句往往需要多条 CPU 指令完成。其实count++包含了三个CPU指令

有序性

即程序执行的顺序按照代码的先后顺序执行。

在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

在单线程的情况下,CPU执行语句并不是按照顺序来的,为了更高的执行效率可能会重新排序,单线程下是可以提高执行效率且保证正确。但在多线程下反而变成了安全问题,Java提供volatile来保证一定的有序性。此处不做深入!

volatile

volatile特性

  • 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入
  • 原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性

面试题】为什么volatile不能保证a++的线程安全问题
:线程执行a++要经历读取主内存-加载-使用-赋值-写内存-写回主内存几个阶段,而且a++不是原子操作,至少可以分为三步执行。线程A、B同时从主内存读取a的值,A线程执行到加载阶段切换上下文交出CPU使用权,B线程完成整个操作并刷新了主内存中a的值。此时A线程继续赋值等其他操作,已经造成了安全问题。可见性是保证线程每次读取时必须读取主内存的值,对后续的操作没有限制,不会因为主内存中的值改变而中断了操作。如果是原子性则可以,synchronized可以保证原子性。

在这里插入图片描述

volatile 的实现原理

有volatile修饰的共享变量进行写操作的时候会使用CPU提供的Lock前缀指令

  • 将当前处理器缓存的数据写回到系统内存
  • 这个写回内存的操作会使其他CPU里缓存了该地址的数据无效

单例模式的双重锁为什么要加volatile


public class TestInstance{
	private volatile static TestInstance instance;
	public static TestInstance getInstance(){        //1
		if(instance == null){                        //2
			synchronized(TestInstance.class){        //3
				if(instance == null){                //4
					instance = new TestInstance();   //5
				}
			}
		}
		return instance;                             //6
	}
}

需要volatile关键字的原因是,在并发情况下,如果没有volatile关键字,在第5行会出现问题。
instance = new TestInstance()可以分解为3行伪代码


a. memory = allocate() //分配内存
 
b. ctorInstanc(memory) //初始化对象
 
c. instance = memory //设置instance指向刚分配的地址

上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行第5行代码时,B线程进来执行到第2行代码。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接跳到第6行并返回一个未初始化的对象

总结

因为CPU与内存的速度差距越来越大,为了弥补速度差距引入了CPU缓存,又因为缓存导致线程安全问题,从前到后缕出一条线来就很容易理解了。如果只是单线程完全不担心什么指令重排,想要更高的执行效率必然付出安全风险。知其然,知其所以然!

到此这篇关于java 多线程与并发之volatile详解分析的文章就介绍到这了,更多相关Java volatile内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: java 多线程与并发之volatile详解分析

本文链接: https://www.lsjlt.com/news/156145.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • java 多线程与并发之volatile详解分析
    目录CPU、内存、缓存的关系CPU缓存什么是CPU缓存为什么要有多级CPU CacheJava内存模型(Java Memory Model,JMM)JMM导致的并发安全问题可见性原子...
    99+
    2022-11-12
  • Java并发编程之Volatile变量详解分析
    目录一、volatile变量的特性1.1、保证可见性,不保证原子性1.2、禁止指令重排二、内存屏障三、happens-beforeVolatile关键字是Java提供的一种轻量级的同...
    99+
    2022-11-12
  • Java并发编程之volatile与JMM多线程内存模型实例分析
    本篇内容主要讲解“Java并发编程之volatile与JMM多线程内存模型实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java并发编程之volatile与JMM多线程内存模型实例分析”...
    99+
    2023-06-30
  • Java并发编程之volatile与JMM多线程内存模型
    目录一、通过程序看现象二、为什么会产生这种现象(JMM模型)?三、MESI 缓存一致性协议 一、通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码...
    99+
    2022-11-13
  • 详解Java多线程与并发
    目录一、进程与线程二、并发与并行1、线程安全问题2、共享内存不可见性问题三、创建线程1、继承Thread类2、实现Runable接口3、实现Callable接口四、Thread类详解...
    99+
    2022-11-12
  • 详解Java并发编程基础之volatile
    目录一、volatile的定义和实现原理1、Java并发模型采用的方式2、volatile的定义3、volatile的底层实现原理二、volatile的内存语义1、volatile的...
    99+
    2022-11-12
  • 详解Java并发编程之volatile关键字
    目录1、volatile是什么?2、并发编程的三大特性3、什么是指令重排序?4、volatile有什么作用?5、volatile可以保证原子性?6、volatile 和 sy...
    99+
    2022-11-12
  • JUC并发编程之volatile详解
    目录   1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1....
    99+
    2023-09-01
    java jvm 开发语言
  • Java多线程并发、并行、线程与进程实例分析
    本篇内容介绍了“Java多线程并发、并行、线程与进程实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、并发与并行并发:指两个或多个事...
    99+
    2023-07-02
  • Java多线程并发编程 Volatile关键字
    volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字。只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatil...
    99+
    2023-05-31
    java volatile 关键字
  • Java中多线程与并发的示例分析
    这篇文章主要介绍Java中多线程与并发的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、进程与线程进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。线程:是进程的一个执行路径,一个...
    99+
    2023-06-15
  • Java并发编程之关键字volatile的示例分析
    这篇文章给大家分享的是有关Java并发编程之关键字volatile的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、作用被 volatile 修饰的变量保证了不同线程对该变量操作的内存可见性禁止指令重排...
    99+
    2023-06-15
  • Java线程之线程同步synchronized和volatile详解
    上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值...
    99+
    2023-05-30
    java 线程 synchronized
  • Java并发编程之关键字volatile的深入解析
    目录前言一、可见性二、有序性总结前言 volatile是研究Java并发编程绕不过去的一个关键字,先说结论: volatile的作用:       &n...
    99+
    2022-11-12
  • 如何深入理解Java多线程与并发框中的volatile关键字
    本篇文章为大家展示了如何深入理解Java多线程与并发框中的volatile关键字,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。概念把对 volatile变量的单个读/写,看成是使用 同一个监视器锁 ...
    99+
    2023-06-05
  • Java多线程之并发编程的核心AQS详解
    目录一、AQS简介1.1、AOS概念 1.2、AQS的核心思想1.3、AQS是自旋锁1.4、AQS支持两种资源分享的方式 二、AQS原理2.1、同步状态的管理2....
    99+
    2022-11-12
  • java高并发的volatile与Java内存模型详解
    public class Demo09 { public static boolean flag = true; public static class T1...
    99+
    2022-11-12
  • java高并发之线程组详解
    目录线程组创建线程关联线程组为线程组指定父线程组根线程组批量停止线程总结线程组 我们可以把线程归属到某个线程组中,线程组可以包含多个线程以及线程组,线程和线程组组成了父子关系,是个树...
    99+
    2022-11-12
  • 理解Java多线程之并发编程
    目录1 多线程的使用场景2 多线程的缺点2.1 上下文切换的开销(1)上下文切换的开销(2)如何减少上下文切换2.2 多线程中的数据一致性问题(1)线程中访问外部数据的过程(2)线程...
    99+
    2023-02-02
    Java并发编程 java并发编程实战 java并发编程的艺术
  • 【Java|多线程与高并发】定时器(Timer)详解
    文章目录 1. 前言2. 定时器的基本使用3. 实现定时器4. 优化上述的定时器代码5. 总结 1. 前言 在Java中,定时器Timer类是用于执行定时任务的工具类。它允许你安排一个...
    99+
    2023-10-05
    java jvm 开发语言
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作