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并发编程学习11-任务执行演示
【5月更文挑战第4天】本篇将结合任务执行和 Executor 框架的基础知识,演示一些不同版本的任务执行Demo,并且每个版本都实现了不同程度的并发性。
20 4
Java并发编程学习11-任务执行演示
|
2天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
11 0
|
2天前
|
缓存 Java 数据库
Java并发编程中的锁优化策略
【5月更文挑战第9天】 在高负载的多线程应用中,Java并发编程的高效性至关重要。本文将探讨几种常见的锁优化技术,旨在提高Java应用程序在并发环境下的性能。我们将从基本的synchronized关键字开始,逐步深入到更高效的Lock接口实现,以及Java 6引入的java.util.concurrent包中的高级工具类。文中还会介绍读写锁(ReadWriteLock)的概念和实现原理,并通过对比分析各自的优势和适用场景,为开发者提供实用的锁优化策略。
3 0
|
2天前
|
算法 安全 Java
深入探索Java中的并发编程:CAS机制的原理与应用
总之,CAS机制是一种用于并发编程的原子操作,它通过比较内存中的值和预期值来实现多线程下的数据同步和互斥,从而提供了高效的并发控制。它在Java中被广泛应用于实现线程安全的数据结构和算法。
17 0
|
3天前
|
存储 安全 算法
掌握Java并发编程:Lock、Condition与并发集合
掌握Java并发编程:Lock、Condition与并发集合
11 0
|
3天前
|
存储 算法 Java
了解Java内存管理与垃圾回收机制
了解Java内存管理与垃圾回收机制
6 0
|
3天前
|
Java 编译器 开发者
Java并发编程中的锁优化策略
【5月更文挑战第8天】在Java并发编程中,锁是实现线程同步的关键机制。为了提高程序的性能,我们需要对锁进行优化。本文将介绍Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级和读写锁等方法,以帮助开发者提高多线程应用的性能。
|
4天前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第7天】在Java中,多线程编程是提高应用程序性能和响应能力的关键。本文将深入探讨Java并发编程的核心概念,包括线程安全、同步机制以及性能优化策略。我们将通过实例分析,了解如何避免常见的并发问题,如死锁、竞态条件和资源争用,并学习如何使用Java提供的并发工具来构建高效、可靠的多线程应用。
|
4天前
|
缓存 Java
Java并发编程:深入理解线程池
【5月更文挑战第7天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将了解线程池的基本概念,以及如何使用Java的Executor框架来创建和管理线程池。此外,我们还将讨论线程池的优点和缺点,以及如何选择合适的线程池大小。最后,我们将通过一个示例来演示如何使用线程池来提高程序的性能。
|
5天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。