1 java内存模型JMM
java内存模型(java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段、静态字段和构成数组对象的元素)的访问方式。
(JVM运行程序的实例是线程,而每个线程创建时,JVM都会为其创建一个工作内存(栈空间),用于存储线程的私有数据,而java内存规定所有内存都必须存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但是线程对变量的操作需要在自身的工作内存中,因此线程就会现将变量从主内存中拷贝到工作内存中,操作完成后,在写回主内存)
JMM中的主内存
1 存储java实例对象(所有线程创建的实例的对象)
2 包括成员变量、类信息、常量、静态变量等
3 属于数据共享的区域,多线程并发操作的时候会引发线程安全问题。
JMM中的工作内存
1 存储当前方法的所有本地变量信息,本地变量对其他线程不可见。
2 字节码行号指示器、Native方法信息
3 属于线程私有数据区域,不存在线程安全问题。
JMM与java内存区域划分是不同的概念层次
1 JMM描述的是一种规则,(通过这种规则控制程序中各个变量在共享数据区域和私有数据区域的访问方式)围绕原子性、有序性、可见性展开。
2 相似点:存在共享数据区域和私有数据区域。(JMM中主内存属于共享数据区域,应该包括堆和方法区;而工作内存私有数据区域,应该包括程序计数器、虚拟机栈、本地方法栈)
主内存和工作内存的数据储存类型以及操作方式归纳
1 根据虚拟机的规范,对于一个实例对象中的成员方法而言,如果方法中包含本地变量是基本数据类型的,这些变量将直接存储在工作内存的栈帧结构中。
2 如果方法中包含本地变量是引用类型的,该变量的引用会存储在工作内存的栈帧中,而对象实例会存储在主内存(即共享数据堆中)。
3.对于实例对象的成员变量(无论什么类型)、static变量、类信息均会被存储在主内存中。
4. 主内存共享的方式时线程哥拷贝一份数据到工作内存,操作完成后刷新回主内存。
2 JMM如何解决可见性问题
可以简单理解为把数据从主内存加载到缓存再到寄存器,计算将结束之后在返回。
指令重排序需要满足的条件
在程序执行时候,为了提高性能,处理器和编译器常常会对指令进行重排序,但是不能随意排序。
1 在单线程环境下,不能改变程序运行的结果。
2 存在数据依赖关系的不允许重排序。
总的来说:无法通过happens-Before原则推导出来的,才能进行指令的重排序。
A操作的结果需要需要对B操作可见,则A与B存在happens-before关系。happens-before关系是判断数据是否存在竞争,线程是否安全的主要依据。
线程A和B需要有happens-Before 的关系,不然j=1 不一定成立 。
happens-Before的八大原则
happens-Before的概念:
如果两个操作不满足上述任一一个happens-Before的规则,那么这两个操作就没有顺序的保障,JVM可以对着两个操作进行重排序。如果操作操作A happens-Before 操作B,那么操作A的内存上所做的操作对操作B都是可见的。
volatile:JVM提供的轻量级同步机制
1 保证volatile修饰的共享变量对所有线程总是可见的。
2 禁止指令重排序优化
volatile的可见性优化:
value的变量的任何变化,都会立马反应到线程当中,但是当多条线程同时调用increase方法时,就会出现线程安全问题,因为value++并不符合原则性。对于increase方法必须使用synchronized 修饰,来解决线程安全问题。
一旦使用了 synchronized 修饰,由于synchronized 也具有和volatile相同的特性,所以volatile就可以省去。
这种场景可以使用volatile达到线程安全
由于对boolean值得修改属于原子操作, 因此通过volatile来修饰boolean值,可以实现对boolean值的修改,对其他线程立即可见,从而达到线程安全。
volatile变量为何立即可见?
当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量值刷新到主内存中;
当读取一个volatile变量时,JMM会把该线程对应的工作内存置为无效。
volatile如何禁止重拍优化?
内存屏障(Memory Barrier)
1.保证特定操作的执行顺序。
2.保证某些变量的内存可见性。
通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化。
强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
单例的双重检测实现(实现线程安全的单例写法)
上述代码可能会发生重排序,如
解决上述问题:用volatile修饰
volatile和synchronized 的区别
1 volatile本质是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前对象,只有当前线程可以访问该变量其他线程被阻塞直到该线程完成变量操作为止。
2 volatile 仅能使用在变量级别;synchronized则可以使用在变量、方法和类级别。
3 volatile仅能实现变量的修改可见性,不能保证原子性;synchronized则可以保证变量修改的可见性和原子性。
4 volatile不会造成线程阻塞;synchronized可能会造成线程阻塞
5 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。