java内存模型(JMM)

java内存模型(JMM)

  1. Java虚拟机规范试图定义一种Java内存模型(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,让Java程序在各种平台上都能达到一致的内存访问效果。
  2. 简单来说,由于CPU执行指令的速度是很快的,但是内存访问的速度就慢了很多,相差的不是一个数量级,所以搞处理器的那群大佬们又在CPU里加了好几层高速缓存。
  3. 在Java内存模型里,对上述的优化又进行了一波抽象。JMM规定所有变量都是存在主存中的,类似于上面提到的普通内存,每个线程又包含自己的工作内存,方便理解就可以看成CPU上的寄存器或者高速缓存。所以线程的操作都是以工作内存为主,它们只能访问自己的工作内存,且工作前后都要把值在同步回主内存。

java内存模型(JMM)_第1张图片

  • 在线程执行时,首先会从主存中read变量值,再load到工作内存中的副本中,然后再传给处理器执行,执行完毕后再给工作内存中的副本赋值,随后工作内存再把值传回给主存,主存中的值才更新。
  • 线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写

    使用工作内存和主存,虽然加快的速度,但是也带来了一些问题

i = i + 1;

假设i初值为0,当只有一个线程执行它时,结果肯定得到1,当两个线程执行时,会得到结果2吗?这倒不一定了。可能存在这种情况:
java内存模型(JMM)_第2张图片

如果两个线程按照上面的执行流程,那么i最后的值居然是1了。如果最后的写回生效的慢,你再读取i的值,都可能是0,这就是缓存不一致问题。,JMM主要就是围绕着如何在并发过程中如何处理原子性、可见性和有序性这3个特征来建立的,通过解决这三个问题,可以解除缓存不一致的问题。而volatile跟可见性和有序性都有关。

什么是内存可见性,什么又是重排序呢?

重排序
在一个线程里,所有的操作都是按顺序的,但是在JMM里其实只要执行结果一样,是允许重排序的..
重排序不会给单线程带来内存可见性问题。多线程中交错执行时,重排序可能会造成内存可见性问题
java内存模型(JMM)_第3张图片
指令重排序 as-if-serial

  1. 不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
  2. 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。
  3. as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的。as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。

什么是内存可见性?

  1. 如果线程A对共享变量X进行了修改,但是线程A没有及时把更新后的值刷入到主内存中,而此时线程B从主内存读取共享变量X的值,所以X的值是原始值,那么我们就说对于线程B来讲,共享变量X的更改对线程B是不可见的。
  2. 要实现共享变量的内存可见行,必须要保证两点。线程修改后的共享变量值能及时的从线程的工作内存刷新到主内存中。其它线程能够及时的把共享变量从主内存刷新到自己的工作内存中

volatile实现可见性

  1. volatile通过内存屏障指令和禁止指令重排序来实现内存可见性
  2. 如果把加入volatile关键字的代码和未加入volatile关键字的代码都生成汇编代码,会发现加入volatile关键字的代码会多出一个lock前缀指令。lock前缀指令实际相当于一个内存屏障,内存屏障提供了以下功能:
1   重排序时不能把后面的指令重排序到内存屏障之前的位置
2 . 使得本CPU的Cache写入内存
3 . 写入动作也会引起别的CPU或者别的内核无效化其Cache,相当于让新写入的值对别的线程可见。

. 对单个volatile变量的读/写具有原子性,但是对于类似volatile++这样的复合操作就无能为力了不能保证原子性

java内存模型(JMM)_第4张图片

synchronized实现可见性和原子性
java内存模型(JMM)_第5张图片
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其 他线程只能依靠阻塞来等待线程释放锁。

实现同步的方式有几种,区别是什么?

  1. synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
  2. lock(显示锁):需要显示指定起始位置和终止位置。一般使用ReentrantLock类做为锁,多个线程中必须要使用一个ReentrantLock类做为对 象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
  3. synchronized是托管给JVM执行的,而lock是java写的控制锁的代码

java内存模型(JMM)_第6张图片

你可能感兴趣的:(java)