java -- volatile关键字
volatile关键字
可见性
private static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (run) {
//运行代码
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
run = false;
log.debug("修改为false"); // 程序打印了修改为false后程序也不会停止
}
运行上面的代码在主线程睡眠一秒后把run改为了false,按理说 这时候t1线程就应该执行完毕停止线程,但是真的执行会发现线程1停不下来了,这个时候主线程的修改对t1线程不可见就发生了可见性问题
因为每个线程都是有工作内存的,因为t1线程高速的获取主内存中的true,jit编译器就会把run缓存到线程的高速工作内存,减少对主存的访问提高效率
volatile解决可见性问题
volatile直接翻译可为易变的,用volatile来修饰变量的时候就不会把变量缓存到线程的工作内存中,保证我们拿到的变量都是从主存中拿到的最新的值
这是发现就可以正确的结束t1线程了
synchronized保证可见性
private static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (run) {
System.out.println("线程执行中");
}
});
t1.start();
TimeUnit.SECONDS.sleep(1);
run = false;
log.debug("修改为false");
}
取消run 变量的volatile修饰,重新运行代码发现代码是会t1线程是会正常停下来的,我们先看看 System.out.println方法的源码
发现println方法是被synchronized修饰的synchronized也是可以保证可见性的这就是如果我们里面加一个打印,t1线程也会正常停止的原因
有序性
指令重排
在看有序性之前要说一下指令重排,因为如果没有指令重拍的话,也就不存在有序性问题了。指令重排:
是编译器和处理器在不影响代码单线程执行结果的前提下,对源代码的指令进行重新排序执行。这种重排序执行是一种优化手段,目的是为了处理器内部的运算单元能尽量被充分利用,提升程序的整体运行效率。
class Demo {
private int num = 1;
private boolean ready = false;
public void method1() {
num = 2;
ready = true;
}
public void method2() {
if (ready) {
int var = num + 1;
}
}
}
假设我们同时开启2个线程一个线程执行method1
,另一个线程执行method2
是不是应该var应该等于3,其实还有一种情况,就是当
num = 2;
ready = true;
// 如果被指令重排
ready = true;
num = 2;
那么这个时候就会出现2的情况,因为编译器和处理器在不影响代码单线程
执行结果的前提下,对源代码的指令进行重新排序执行,因为在单的线程中运行method1方法,不论是先赋值num
还是先赋值ready
其运行的顺利都不会影响运行method1方法的线程,但是确影响了其他线程执行的结果。
内存屏障
volatile内存屏障分为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。内存屏障的作用就是为了阻止指令重排和把写缓冲区中的数据全部刷新的内存中,volatile关键字读前插读屏障,写后加写屏障,同时禁止两侧代码重排序,实现多线程之间数据的可见性。
本文地址:https://blog.csdn.net/qq_43539962/article/details/111875755