JUC学习笔记——共享模型之内存
作者:互联网
Java内存模型
我们首先来介绍一下Java内存模型:
- JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。
JMM的主要作用如下:
- 计算机硬件底层的内存结构过于复杂
- JMM的意义在于避免程序员直接管理计算机底层内存,用一些关键字synchronized、volatile等可以方便的管理内存。
JMM主要体现在三个方面:
- 原子性 - 保证指令不会受到线程上下文切换的影响 (我们在管程已经介绍过了)
- 可见性 - 保证指令不会受 cpu 缓存的影响
- 有序性 - 保证指令不会受 cpu 指令并行优化的影响
可见性
这一小节我们来介绍可见性
可见性问题
首先我们根据一段代码来体验什么是可视性:
// 我们首先设置一个run运行条件设置为true,在线程t运行1s之后,我们在主线程修改run为false希望停下t线程
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
sleep(1);
run = false;
}
// 线程t不会如预想的停下来!
我们进行简单的分析:
- 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
- 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
- 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量 的值,结果永远是旧值
可见性解决
我们提供两种可见性的解决方法:
- volatile(易变关键字)
// 它可以用来修饰成员变量和静态成员变量
// 他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
// 我们首先设置一个run运行条件设置为true,在线程t运行1s之后,我们在主线程修改run为false希望停下t线程
static volatile boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
sleep(1);
run = false;
}
// 这时程序会停止!
- synchronized(锁关键字)
// 我们对线程内容进行加锁处理,synchronized内部会自动封装对其主存进行查找
static Object obj = new Object();
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
synchronized(obj){
while(run){
// ....
}
}
});
t.start();
sleep(1);
run = false;
}
// 这时程序会停止!
可见性解决方法对比
我们对volatile和synchronized两种方法进行简单对比:
- volatile只能保证可见性和有序性,synchronized可以保证可见性,有序性和原子性
- volatile属于轻量级操作,synchronized属于重量级操作;前者的各部分消耗量较少,性能较高
我们在这里介绍一下为什么synchronized能进行可见性问题解决: