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 全栈知识体系

相关文章
|
11月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
1180 3
|
12月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
10月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
300 4
|
前端开发 Java C++
JUC系列之《CompletableFuture:Java异步编程的终极武器》
本文深入解析Java 8引入的CompletableFuture,对比传统Future的局限,详解其非阻塞回调、链式编排、多任务组合及异常处理等核心功能,结合实战示例展示异步编程的最佳实践,助你构建高效、响应式的Java应用。
|
10月前
|
设计模式 算法 安全
JUC系列之《深入理解AQS:Java并发锁的基石与灵魂 》
本文深入解析Java并发核心组件AQS(AbstractQueuedSynchronizer),从其设计动机、核心思想到源码实现,系统阐述了AQS如何通过state状态、CLH队列和模板方法模式构建通用同步框架,并结合独占与共享模式分析典型应用,最后通过自定义锁的实战案例,帮助读者掌握其原理与最佳实践。
|
10月前
|
缓存 安全 Java
JUC系列《深入浅出Java并发容器:CopyOnWriteArrayList全解析》
CopyOnWriteArrayList是Java中基于“写时复制”实现的线程安全List,读操作无锁、性能高,适合读多写少场景,如配置管理、事件监听器等,但频繁写入时因复制开销大需谨慎使用。
|
10月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
10月前
|
设计模式 缓存 Java
【JUC】(4)从JMM内存模型的角度来分析CAS并发性问题
本篇文章将从JMM内存模型的角度来分析CAS并发性问题; 内容包含:介绍JMM、CAS、balking犹豫模式、二次检查锁、指令重排问题
255 1
|
11月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
1085 17
|
12月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。