面试之内存模型
https://blog.csdn.net/qq_44815020/article/details/124345492
一、相关概念
Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式,JMM决定一个线程对共享变量的写入何时对另一个线程可见,线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。
方法区
方法区也是线程共享区用于储存虚拟机加载的类信息(类的版本、字段、方法、接口),常量,静态变量,即时编译后的代码等数据
方法区逻辑上属于堆的一部分,但是与堆进行了区分,通常又叫非堆。
其中包括了常量池:
运行时常量池,存储字面量和符合 引用,
字符串常量池,
class文件常量池,
在并发编程领域,有两个关键问题:线程之间的通信和同步
在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。
在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()。
Java的并发采用的是共享内存模型
.
2.内存模型中的可见性,原则性,有序性
原子性(多线程情况下)
原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
用i++来做为举例,需要read,load,use,等等的操作,单个操作是原子性的,但是对应整个操作i++来说是不能保证原子性的,
b,可见性
可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
保证可见性的方法:
- volatile(让其他线程的工作内存中的数据失效,重新从主存中load)
- synchronized(unlock之前,写变量值回主存)
- final(一旦初始化完成,其他线程就可见)。
c,有序性
从单线程来看,java代码看是有序的,但是java编译器在编译代码的时候,为了提高性能等因素,会考虑进行指令重排,不影响原意的情况下,原有书序被打乱
二、支撑Java内存模型的基础原理
1.指令重排序
在执行程序时,为了提高性能,编译器和处理器会对指令做重排
2.内存屏障(Memory Barrier )
用来禁止指令重排。
如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。
volatile是基于内存屏障的实现
3.数据依赖性
如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。
编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。
4.as-if-serial
不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义。
指令重排的基本原则:也叫happen-before
- 程序顺序原则:一个线程内保证语义的串行性
- volatile规则:volatile变量的写,先发生于读
- 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
- 传递性:A指令先于B指令,B指令先于C指令 那么A指令必然先于C指令
- 线程的start方法先于它的每一个动作
- 线程的所有操作先于线程的终结(Thread.join())
- 线程的中断(interrupt())先于被中断线程的代码
- 对象的构造函数执行结束先于finalize()方法