JUC并发编程-共享模型之内存
3.1Java内存模型
JMM 即 Java Memory Model,它定义了主存(所有线程都共享的数据,可以认为对应着物理内存)、工作内存(每个线程私有的数据,可以认为对应着缓存)抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。
JMM 体现在以下几个方面
- 原子性 - 保证指令不会受到线程上下文切换的影响
- 可见性 - 保证指令不会受 cpu 缓存的影响
- 有序性 - 保证指令不会受 cpu 指令并行优化的影响
3.2可见性
3.2.1退不出的循环
main 线程对 run 变量的修改对于 t 线程不可见,导致了 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不会如预想的停下来
}
3.2.2分析
-
初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
-
因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
-
1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
3.2.3解决方法
-
volatile(易变关键字)
-
可以用来修饰成员变量和静态成员变量
-
可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
volatile static boolean run = true;
-
-
synchronized
- 通过加锁同一对象来解决变量变动的问题
- synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。
- 但缺点是synchronized 是属于重量级操作,性能相对更低
static boolean run = true; static Object lock=new Object(); public static void main(String[] args) throws InterruptedException { Thread t = new Thread(()->{ while(true){ synchronized (lock){ if(!run) break; } } }); t.start(); sleep(1); //因为加锁的是同一对象 synchronized (lock) { // 线程t如预想的停下来 run = false; } }
3.2.4原子性和可见性
volatile关键字体现的实际就是可见性
- 它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见,不能保证原子性,仅用在一个写线程,多个读线程的情况
3.2.5两阶段终止模式
private Thread thread;
private volatile boolean stop = false;
public void start(){
thread = new Thread(() -> {
while(true) {
if(stop) {
log.debug("料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("将结果保存");
} catch (InterruptedException e) {
}
// 执行监控操作
}
},"监控线程");
thread.start();
}
public void stop() {
stop = true;
//当if判断完,就不再执行sleep方法
thread.interrupt();
}
3.2.6同步模式之Balking
//判断是否执行过start方法
private volatile