JUC并发编程之JMM细讲,面试突击这篇就够了

       在复习juc的线程时发现,JMM也是经常被提问到的一个知识点 ,如果感觉有用,请给我一个赞吧Ciallo~(∠・ω< )⌒★。

       这篇文章主要从以下几个角度来分析理解:(1)JMM是什么;(2)JMM定义了什么;(3)8种内存交互操作都有什么;(4)讲一下volatile关键字。

1、JMM是什么

      

       JMM(Java Memory Model,Java内存模型)是Java平台的核心概念之一。由于不同硬件厂商和操作系统在内存访问机制上存在差异,可能导致相同代码在不同系统上出现不一致的行为。JMM通过屏蔽这些底层差异,确保Java程序在各个平台上都能实现一致的并发效果。

       根据JMM规范,所有变量(包括实例变量和静态变量,但不含局部变量和方法参数)都存储在主内存中。每个线程都拥有独立的工作内存,其中保存了该线程使用变量的主内存副本。线程对变量的所有操作都在工作内存中进行,无法直接读写主内存中的变量。

      线程之间也无法直接访问彼此的工作内存。任何线程间变量值的传递都必须通过主内存来完成。这种机制确保了线程间的数据隔离和一致性。

       值得注意的是,JMM常被误认为与JVM内存结构(如堆、栈、垃圾回收等)相关。实际上,当面试中提到Java内存模型时,通常是在考察多线程和并发编程相关的知识,而非JVM内存管理机制。如果听起来抽象的话,可以画张图给你看看:

2、JMM定义了什么

        这个简单,整个Java内存模型实际上是围绕着三个特征建立起来的。分别是:原子性可见性有序性。这三个特征可谓是整个Java并发的基础。

(1)原子性指的是一个操作是不可分割,不可中断的,一个线程在执行时不会被其他线程干扰。

(2)可见性是指当一个线程修改共享变量时,其他线程能够立即感知到这一变化。在Java中,volatile关键字是实现可见性的重要机制。当一个变量被volatile修饰时,任何对该变量的修改都会立即刷新到主内存中;当其他线程读取该变量时,会直接从主内存中获取最新值。相比之下,普通变量无法保证这种即时可见性。除了volatilefinalsynchronized也能实现可见性。  

   synchronized的可见性机制在于:线程在执行完同步代码块后,在释放锁(unlock)之前,必须将共享变量的值同步到主内存中。

        对于final修饰的字段,一旦初始化完成,只要没有发生对象逸出(即对象在未完全初始化前被其他线程访问),该字段的值对其他线程就是可见的。

(3)有序性 在Java中,可以通过synchronized或volatile关键字来确保多线程操作的有序性,但两者的实现机制有所不同:volatile关键字通过内存屏障机制来禁止指令重排序,从而保证操作的有序性;synchronized关键字则是通过互斥锁机制实现:当一个线程获取锁后,必须释放锁,其他线程才能继续获取锁,这使得被synchronized修饰的代码块在多线程环境下以串行方式执行。

3、讲一下八种内存交互操作

  • lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
  • read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
  • load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
  • use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
  • store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
  • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

        再补充一下JMM对8种内存交互操作制定的规则吧:

  • 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
  • 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
  • 不允许线程将没有assign的数据从工作内存同步到主内存。
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
  • 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
  • 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。

4、介绍一下volatile关键字

     在并发编程中,volatile关键字主要发挥以下两个关键作用:

  • 确保线程间变量的可见性
  • 防止CPU指令重排序

    但是值得注意的是,volatile不保证线程安全(即操作的原子性)。

   (1)可见性,volatile修饰的变量,当一个线程改变了该变量的值,其他线程是立即可见的。普通变量则需要重新读取才能获得最新值。

   (2)volatile 不能保证原子性,尽管 volatile 保证了变量的可见性,但它并不能保证操作的原子性。原子性是指一个操作要么全部执行,要么全部不执行,不会被其他线程打断。例如,i++ 操作实际上包含了读取、修改和写入三个步骤,这些步骤在多线程环境下可能会被其他线程打断,导致数据不一致。

   (3)禁止指令重排序,为了充分发挥CPU性能并提升程序执行效率,在确保程序最终结果与顺序执行一致的前提下,指令的实际执行顺序可以与代码逻辑顺序不同,这一过程称为指令重排序。指令重排序主要分为三种类型:编译器重排序、指令级并行重排序以及内存系统重排序。具体过程如下:

        指令重排序在单线程是没有问题的,不会影响执行结果,而且还提高了性能。但是在多线程的环境下就不能保证一定不会影响执行结果了。所以在多线程环境下,就需要禁止指令重排序

图片来源:面试官问我什么是JMM - 知乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值