并发编程之Java内存模型

2020-02-15  本文已影响0人  小小一技术驿站

@TOC

5.1 Java内存模型

JMM即Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存、CPU指令优化等。
JMM体现在以下几个方面

5.2 可见性

退不出的循环
先来看一个现象,main线程对run变量的修改对于t线程不可见,导致了t线程无法停止 :


在这里插入图片描述

为什么呢?分析一下 :
1.初始状态,t线程刚开始从主内存读取了run的值到工作内存。


在这里插入图片描述
  1. 因为t线程要频繁从主内存中读取run的值,JIT编译器会将run的值缓存至自己工作内存中的高速缓存中,减少对主存中run的访问,提高效率


    在这里插入图片描述
  2. 1秒之后,main线程修改了run的值,并同步至主存,而t是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
    在这里插入图片描述
    解决方法
    volatile(易变关键字)
    它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。
    可见性 VS 原子性
    前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对volatile变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况 :
    上例从字节码理解是这样的 :
    在这里插入图片描述
    比较一下之前我们将线程安全时举的例子 :两个线程一个i++ 一个i--,只能保证看到最新值,不能解决指令交错
    在这里插入图片描述
    注意
    synchronized语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能相对更低。
    如果在前面示例中的死循环中加入System.out.println()会发现即使不加volatile修饰符,线程t也能正确看到对run变量的修改了,想一想为什么?

5.3 有序性

JVM会在不影响正确性的前提下,可以调整语句的执行顺序 :


在这里插入图片描述

可以看到,至于是先执行i还是先执行j,对最终的结果不会产生影响。所以,上面代码真正执行时,既可以是


在这里插入图片描述
也可以是
在这里插入图片描述

这种特性称之为指令重排,多线程下指令重排会影响正确性。

volatile原理

volatile的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

  1. 如何保证可见性

double-checked locking 单例模式为例

在这里插入图片描述

以上的实现特点是 :

4.double-checked locking 解决

在这里插入图片描述
字节码上看不出来volatile指令的效果
在这里插入图片描述
在这里插入图片描述
happens-before
happens-before规定了对共享变量的写操作对其它线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JMM并不能保证一个线程对共享变量的写,对于其它线程对该共享变量的读可见
上一篇下一篇

猜你喜欢

热点阅读