JUC 学习(一) 之 volatile 关键字
首先我们来看这么一段代码:
package indi.qiaolin.juc;
import lombok.Data;
/**
* 测试JUC volatile 关键字
*
* @author qiaolin
* @version 2018年7月8日
*
*/
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while(true){
if(td.isFlag()){
System.out.println("VolatileTest.main");
break;
}
}
}
}
@Data
class ThreadDemo implements Runnable{
private boolean flag;
public void run() {
setFlag(true);
}
}
你觉得最后的结果是不是控制台输出:VolatileTest.main 并且程序结束?
其实这个程序有80%的几率不会结束!陷入下面这个死循环!
while(true){
if(td.isFlag()){
System.out.println("VolatileTest.main");
break;
}
}
而陷入死循环的可能只有一种就是td这个对象的flag属性一直为false; 但是我们明明在td的run方法中设置成true了的啊!
public void run() {
setFlag(true);
}
其实造成这个问题的情况就是 多个线程共享资源不可见的问题!每一个线程访问共享数据时都会先把数据读过来,然后如果有修改值的操作,就把改好的数据放回去!假设这个数据是在堆中!我们两个线程一个读一个写,那么这两个线程都是先把数据读到自己的缓存中,然后再操作,这时候如果A是读,B是写,那么如果A先读了数据,B后面去写,然后B把写好的数据放回到堆中,照理说A需要刷新他缓存中的数据,但是A如果处于一个高效率执行的情况下,例如 while(true) 他就会没时间去刷新或者刷新很慢这种情况,导致A所在的数据并不是已经被B修改过的数据,这个时候就出现了这个情况 内存可见性 !
下面是比较详细内存可见性的解释:
1、内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
2、可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
来一套图说明下这个问题把!如果有错请提出来!多多交流指教!!
首先这两句代码创建了一个td对象, 并启动一个流程;我们暂且称这个流程为流程1
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
流程1虽然已经启动,但是他肯定没有抢到执行权,此时main线程继续走,进入了这个while循环,但是while(true)的效率,那是相当的高,主要是他不用去调用其他对象或者运算!
while(true){
if(td.isFlag()){
System.out.println("VolatileTest.main");
break;
}
}
此时main 已经读了flag到自己的缓存
可能在执行中 线程1抢到了执行权
他先读取了flag数据,然后修改,然后写回去!
但是main线程在高效率的情况下,他一直猛干,根本就不去刷新,拿到的还是缓存的值 就导致了main线程中的td.isFlag()始终是false!
不过我运行这个程序多次,我发现有时候还是会去刷新的,并不是百分百的会死循环!但是有一定的几率!于是我又做了个实验,让他这个代码变得慢一点循环!
while(true){
if(td.isFlag()){
System.out.println("VolatileTest.main");
break;
}
System.out.println("main");
}
加了一句输出,你就会发现,他居然没那么容易死循环了!
但原因并不是效率太高的问题,而是他们两个线程不可见的问题,那我们每次都要多加上几句其他代码来解决? 这肯定不是解决方案啊!这么low,怎么阔能!
解决方案有两种:
1、加上 synchronized 关键字,这个同步关键字会刷新当前线程的缓存!
2、我们只需要把多个线程共享的数据使用volatile关键字, volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同;对于多线程,不是一种互斥关系(当前所对象只能进来一个)但是又不能保证变量状态的“原子性操作”
如果你只是想让一个属性多个线程可见的话就用 volatile把!
例:
@Data
class ThreadDemo implements Runnable{
private volatile boolean flag;
public void run() {
setFlag(true);
}
}