JUC并发编程——JAVA内存模型

简介: JUC并发编程——JAVA内存模型

正文


一、CPU缓存结构


       由于CPU的运算速度比主存(物理内存)的存取速度快很多,为了提高处理速度,现代CPU不直接和内存进行通信,而是在CPU和主存之间设计了高速缓存(Cache),越靠近CPU层的高速缓存速度越快,容量越小。如下图


222.png


每一级高速缓存中所存储的数据都是下一级高速缓存中的一部分,L1最靠近CPU所以读取速度最快。L1和L2高速缓存只能被一个单独的CPU内核使用,L3高速缓存可以被同一个CPU芯片上的所有CPU内核共享,而主内存可以由系统中所有的CPU共享。CPU读取数据时,首先从L1层高速缓存中读取数据,如果没有读取到,再到L2,L3高速缓存中读取数据,如果都没有读取到数据,就会去主存中读取数据。


二、并发编程的三大问题


原子性


       原子性就是指不可中断的一个或者一些列操作,不能被线程调度机制打断的操作。如下面代码


public class Test01 {
    private int count=0;
    public void increase(){
        System.out.println(count++);
    }
}


将该java文件通过一下指令编译成class文件。javac -encoding UTF-8 Test01.java。通过javap反编译。


333.png


可知++操作并不是原子性的,而是进行了取值,运算,赋值,返回四个操作,在多线程并发的情况下,会发生原子性的问题,所以不是线程安全的。


可见性


一个线程对共享变量的修改,另一个线程能够立即可见,那么这个变量具有内存可见性。


JAVA内存模型


Java内存模型只是一种规范,抽象的概念,不是具体存在的。注意和JVM内存结构不同。


java内存模型规定


1、所有的变量存储在主内存中


2、每一个线程都有自己的工作内存,且对变量的操作都是在自己的工作内存中进行的。


3、不同的线程之间无法直接访问彼此工作内存中的变量,要想访问只能通过主存来传递(线程通信)


222.png


在java中,所有的局部变量,方法定义的参数都不会在线程之间共享,所以也就没有可见性的问题。而定义在堆中的共享变量,类,数组元素等都是线程共享的,存在可见性的问题。解决可见性问题可以使用volatile关键字解决。


有序性


       有序性是指程序执行的顺序按照代码的先后顺序执行。但是在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序,它不保证程序各个语句的执行顺序严格按照代码顺序执行,但是它会保证程序最终的结果和顺序执行结果一致。但是这只是针对单线程运行,如果在多线程条件下,重排序会出现内存可见性问题,导致线程不安全。


重排序分三种类型


1、编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。


2、指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。


3、内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。


JAVA中synchronized、volatile可以解决重排序。


三、JMM内存模型


       JMM规定将所有的变量(不包括局部变量)都存放在公共内存中,当线程使用变量时会把主存的变量复制到自己的工作空间(私有内存),线程对变量的读写操作,都是在自己的工作空间内完成的。操作完成之后,再把自己私有内存的变量刷新到主内存中。但是如果两个或者多个线程同时操作同一个变量就会发生可见性问题。


       JMM定义了一套自己的主存与工作内存之间的交互协议,即变量如何从主存到工作内存,又如何从工作内存写入到主内存。该协议有8种操作,并且要求JVM具体实现必须保证其中每一种操作都是原子性的。


222.png


这八种操作必须满足以下规则


不允许read和load、store和Write操作单独出现。以上两个操作必须按照顺序执行,但没有保证必须连续执行,也就是在read/load、store/write之间可以插入其他指令。

不允许一个线程丢弃它最近的assign操作,也就是线程使用assign赋值之后,必须Write到主存中。

不允许一个线程无原因(没有assign操作)把数据从线程的工作内存同步到主存中。

一个新的变量只能从主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,也就是说一个变量必须经过load和assign之后行use和store。

一个变量在同一时刻只允许一个线程给它执行lock操作。但lock操作可以被同一个线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,才会释放对象。

如果对一个变量执行lock,将会清空工作内存中此变量的值,执行引擎使用这个变量前,需要重新load或者assign操作初始化变量的值。

如果一个变量事先没有被lock锁定,就不允许执行unlock,也不允许unlock被其他线程锁定的变量。

对一个变量执行unlock操作之前,必须把此变量同步回主存(执行 store和Write)


四、JMM如何解决有序性问题


       JMM提供了自己的内存屏障指令,要求JVM编译器实现这些指令,禁止特定类型的编译器和CPU重排序(不是所有的编译器重排序都要禁用)。


       JMM内存屏障主要有Load和Store两类


Load Barrier(读屏障):在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主存中加载数据。


Store Barrier(写屏障):在写指令之后插入写屏障,能让写入缓存的最新数据写回主存。


但是在实际使用时,会对Load Barrier和Store Barrier 组合使用


LoadLoad(LL)屏障:在执行预加载的指令序列中,通常需要显示的声明LoadLoad屏障,因为这些Load指令可能会依赖其他CPU执行的Load指令结果。


StoreStore(SS)屏障:通常情况下CPU不能保证从高速缓存向主存按顺序刷新数据,那么就需要SS,屏障。例如 store1;storestore;store2; 在Store2以及后续的写入操作之前,可以及时看到Store1对变量的操作。


LoadStore(LS)屏障:该屏障用于在数据写入操作执行前确保完成数据读取。


StoreLoad(SL)屏障:该屏障用于数据在读取执行操作之前,确保完成数据的写入。是开销最大,兼具其他三种屏障效果,在现代的多核CPU大多支持该屏障。


参考:


《JAVA高并发核心编程(卷2):多线程、锁、JMM、JUC、高并发设计》-尼恩编著

Java 并发 - 理论基础 | Java 全栈知识体系

相关文章
|
1月前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
31 0
|
1月前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
39 8
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
1月前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
1月前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
30天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
43 0
|
7月前
|
Java C++
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
54 5
|
4月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
6月前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
63 1
|
6月前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。