JMM-可见性
1.概念
当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
- 串行程序不存在可见性问题 。因为你在任何一个操作步骤中修改某个变量,那么在后续的步骤中,读取这个变量的值,一定是修改后的新值
- 并行程序中如果一个线程修改了某一个全局变量,那么其他线程未必可以马上知道这个改动
//共享变量可见性问题
static boolean run = true;
@Test
public void test1() {
Thread t1 = new Thread(() -> {
while (run) {
//...
}
}, "t1");
t1.start();
try {
Thread.sleep(1000);
//在主线程中修改run的值,t1线程不会停止(可见性问题)
run = false;
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.可见性原因分析
.1.初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存
2.因为 t 线程要频繁从主内存中读取 run 的值,
JIT 编译器
会将 run 的值缓存至自己工作内存
中的高速缓存中,减少对主存中 run 的访问,提高效率。
3.1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值。
3.解决方法
1.volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
。
//添加volatile后,线程会一直在主存中读取值,不再使用缓存。
volatile static boolean run = true;
@Test
public void test1() {
Thread t1 = new Thread(() -> {
while (run) {
//...
}
}, "t1");
t1.start();
try {
Thread.sleep(1000);
run = false; //t1线程会停止
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.可以使用
synchronized
保证共享变量可见性
//使用synchronized解决可见性问题
static boolean flag = true;
static final Object lock = new Object();
@Test
public void test2() {
Thread t1 = new Thread(() -> {
while (run) {
synchronized (lock) {
//...
if (!run) {
break;
}
}
}
}, "t1");
t1.start();
try {
Thread.sleep(1000);
run = false; //t1线程会停止
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4.可见性和原子性区别
可见性
保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况: 上例从字节码理解是这样的:
比较一下之前我们将线程安全时举的例子:两个线程一个 i++ 一个 i-- ,只能保证看到最新值,不能解决指令交错。
synchronized
语句块既可以保证代码块的原子性
,也同时保证代码块内变量的可见性
。但缺点是synchronized 是属于重量级操作,性能相对更低。
如果在前面示例的死循环中加入 System.out.println() 会发现即使不加 volatile 修饰符,线程 t 也能正确看到对 run 变量的修改了,想一想为什么?
//在while块中添加 System.out.println();
static boolean run= true;
@Test
public void test3() {
Thread t1 = new Thread(() -> {
while (run) {
//...
System.out.println("sout");
}
}, "t1");
t1.start();
try {
Thread.sleep(1000);
run= false; //t1线程会停止
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
原因分析:
println()方法使用了synchronized
public void println(String x) {
//println方法使用了synchronzied
synchronized (this) {
print(x);
newLine();
}
}