网课链接: 黑马程序员java并发.
第五章:共享模型 内存
第五章 共享模型之内存
章节总结
本章重点讲解了JMM(Java Memory Model)中的
- 可见性: 由JVM缓存优化引起
- 有序性: 由JVM指令重排序优化引起
- happens-before 规则
原理方面
- CPU指令并行
- volatile
模式方面
- 两阶段终止模式的volatile改进
- 同步模式之balking
5.1 Java内存模型
JMM 即 Java Memory Model,它定义了主存(共享内存)、工作内存(线程私有)抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。
JMM 体现在以下几个方面
- 原子性 - 保证指令不会受到线程上下文切换的影响
- 可见性 - 保证指令不会受 cpu 缓存的影响
- 有序性 - 保证指令不会受 cpu 指令并行优化的影响
5.2 可见性
1. 退不出循环
退不出循环的问题可以归咎于JIT编译器的特性, 在JIT编译器中. 当一段代码出现循环, 编译器会将读取的值存入线程的缓存. 因为线程缓存只能在线程内进行更改, 所以线程会进入死循环.
解决方法
- 使用volatile
- 它可以用来修饰成员变量和静态成员变量(放在主存中的变量),他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
public static volatile boolean run = true;
2. 可见性 与 原子性
保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况。
注意
synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是 synchronized 是属于重量级操作,性能相对更低。
如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到 对 run 变量的修改了,想一想为什么?
- 因为 printIn() 方法使用了 synchronized 同步代码块,可以保证原子性与可见性,它是 PrintStream 类的方法。
3. 终止模式 之 两阶段终止模式
Two Phase Termination
在一个线程T1中如何“优雅”终止线程T2? 这里的优雅
就是指给线程一个料理后事的机会
使用 volatile 关键字来实现两阶段终止模式。
错误思路
与两阶段终止模式相反的就是
- 使用线程对象的
stop()
方法停止线程- stop方法会杀死线程, 如果线程锁住了共享资源, 那么当它杀死后其他线程将永远无法获取锁
- 使用
System.exit()
方法停止线程- 用力过猛了, 终止线程编程终止程序
public class Code_02_Test {
public static void main(String[] args) throws InterruptedException {
Monitor monitor = new Monitor();
monitor.start();
Thread.sleep(3500);
monitor.stop();
}
}
class Monitor {
Thread monitor;
// 设置标记,用于判断是否被终止了
private volatile boolean stop = false;
/**
* 启动监控器线程
*/
public void start() {
// 设置线控器线程,用于监控线程状态
monitor = new Thread() {
@Override
public void run() {
// 开始不停的监控
while (true) {
if(stop) {
System.out.println("处理后续任务");
break;
}