一、Java 内存模型
JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。
JMM 体现在以下几个方面:
(1)原子性:保证指令不会受到线程上下文切换的影响
(2)可见性:保证指令不会受 CPU 缓存的影响
(3)有序性:保证指令不会受 CPU 指令并行优化的影响
二、可见性
1. 退不出的循环
2. 解决方法
volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。
3. 可见性 vs 原子性
前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况。
上例从字节码理解是这样的:
比较一下之前我们将线程安全时举的例子:两个线程一个 i++ 一个 i-- ,只能保证看到最新值,不能解决指令交错
注意:
(1)synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低。
(2)如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到对 run 变量的修改了,想一想为什么?
三、模式之 Balking
Balking (犹豫)模式用在一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做了,直接结束返回。
四、有序性
1. 诡异的结果
I_Result 是一个对象,有一个属性 r1 用来保存结果,问,可能的结果有几种?
volatile 修饰的变量,可以禁用指令重排
2. volatile 原理
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
(1)对 volatile 变量的写指令后会加入写屏障
(2)对 volatile 变量的读指令前会加入读屏障
2.1 如何保证可见性
2.2 如何保证有序性
还是那句话,不能解决指令交错:
(1)写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
(2)而有序性的保证也只是保证了本线程内相关代码不被重排序
3. happens-before
happens-before 规定了对共享变量的写操作对其他线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下 happens-before 规则,JMM 并不能保证一个线程对共享变量的写,对于其他线程对该共享变量的读可见。