一、什么是并发编程的可见性?
在并发编程中,“可见性”是指一个线程对共享变量的修改是否能被其他线程及时看到的特性。
二、不可见情况的测试
现在设置成员属性flag=true,如果flag=true则t1线程一直死循环执行任务,main线程设置flag=false,那么t1线程按道理应该结束任务,代码如下:
public class NoVisibleTest {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
// 死循环执行任务
}
System.out.println("t1线程任务执行结束");
}, "t1").start();
// 主线程休眠1s,等待t1进入循环
Thread.sleep(1000);
// 修改flag为false
flag = false;
System.out.println("main线程执行结束");
}
}
可执行结果是 main线程执行结束,t1线程仍在一直死循环执行。要想搞清楚其中的原理就要理解底层。
三、计算机硬件简单分布结构
每一个CPU都会有自己的三级缓存(L1、L2、L3),并且自己CPU的三级缓存不能被其他CPU访问,内存的访问速度相对于CPU实际上是很慢的,让CPU直接访问内存效率很低,所以加入三级缓存提高CPU的执行效率
但是CPU执行修改操作时只是将三级缓存给修改了,并没有修改内存,这就导致虽然main中的flag=false,但是t1线程并不知道。
四、解决方案
解决方案一:
在flag属性前加上volatile关键字修饰,
private static volatile boolean flag = true;
作用是令三级缓存中的flag属性失效,这样每次访问flag都要从内存重新读取,每次修改都会立刻同步到内存
解决方案二:
在while循环内部加上synchronized,如:
while (flag) {
synchronized(NoVisibleTest.class) {
}
}
原理是synchronized每次抢到锁、释放锁,都会将三级缓存与主存进行同步,这就保证了数据的可见性
其他解决方案:
在while循环内部使用System.out.println()语句,因为内部使用了synchronized;
使用AtomicInteger类,内部使用了volatile关键字