1. Java 内存区域
2.Java内存模型(JMM)概述
JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝
对于一个实例对象中的成员方法而言,如果方法中包含本地变量是基本数据类型(boolean,byte,short,char,int,long,float,double),将直接存储在工作内存的帧栈结构中,但倘若本地变量是引用类型,那么该变量的引用会存储在功能内存的帧栈中,而对象实例将存储在主内存(共享数据区域,堆)中。
(数组是引用)
3.Java内存模型(JMM)的承诺
- 原子性: 原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响
- 指令重排:
编译器优化重排:
编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序指令并行重排:
指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序内存系统重排:
由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差
- 可见性: 可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值
4.JMM保护线程安全的解决方案
1.happens-before原则(不满足下述规则的程序线程不安全)
a.程序顺序原则: 在一个线程内必须保证代码顺序执行.
b.锁规则: 必须先解锁后枷锁
c.volatile规则: volatile变量的写先发生于读
d.线程启动规则: 线程的start()方法先于它的每个动作
e.传递性: A先于B,B先于C,那么A先于C
f.线程终止规则: 线程的所有操作先于线程的终结
g.线程中断规则: 对线程interrupt()方法的调用先于线程发生中断
5.volatile关键字
保证被volatile修饰的共享变量对所有线程是可见的.当一个线程修改了一个被 volatile修饰共享变量的值,可以被其他线程立即得知.
实现: 当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变 量值刷新到主内存中,当读取一个volatile变量时,JMM会把该线程对应的工作内 存置为无效,那么该线程将只能从主内存中重新读取共享变量禁止指令重排序优化.
实现: 内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个,一是保 证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile 的内存可见性)。由于编译器和处理器都能执行指令重排优化。如果在指令间插入 一条MemoryBarrier则会告诉编译器和CPU,不管什么指令都不能和这条MemoryBarrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行 重排序优化。Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据, 因此任何CPU上的线程都能读取到这些数据的最新版本。volatile保证线程安全的必要条件
1.对变量的写操作不依赖于当前值
2.该变量没有包含在具有其他变量的不变式中
例:下界小于上界的不变式
4.什么时候使用volatile变量
- 声明标志变量