可见性:
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。要了解多线程并发中的线程可见性, 首先需要了解下JMM,见图
java内存模型中定义了8中操作都是原子的,不可再分的。
lock(锁定):作用于主内存中的变量,它把一个变量标识为一个线程独占的状态;
unlock(解锁):作用于主内存中的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便后面的load动作使用;
load(载入):作用于工作内存中的变量,它把read操作从主内存中得到的变量值放入工作内存中的变量副本
use(使用):作用于工作内存中的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作;
assign(赋值):作用于工作内存中的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作;
store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送给主内存中以便随后的write操作使用;
write(操作):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
当在线程中读取一个变量的时候,首先将变量从系统空间经过 read和load 读取并加载到用户内存,然后对其进行使用和赋值,最后再将操作后的数据 经过 store和write 同步到系统内存。
一般的共享变量在被多线程操作时由于各种原因,更多的是对变量副本进行操作,并只是在用户内存(寄存器)中存储,并不能及时的将数据同步到系统内存,另外,读取数据时也存在一定的可能性直接读取用户空间数据,对其进行操作,这样,操作的结果就不能及时的被其它用户线程所同步到。线程可见性也就无法得到保证,下图是做的例子
1,开始启动一个线程(thread1)判断是否每次都能读到最新的count值
2,一秒后,启动另一个线程(thread2)对变量进行相反值赋值操作
3,结果thread1中count值每一次都一样,导致线程无法结束
结论:共享变量count存在可见性问题
要解决可见性问题,就可以用volatile关键字修饰共享变量。效果如图
由图可知,与上面实例的区别只是将volatile修饰了count。但执行结果却大相径庭,thread1即时终止了,说明thread2中的count值被修改后及时同步到了系统内存,而每次count值也都是从系统内存读取,从而保证了该共享变量的可见性。
其实,volatile 主要是通过内存屏障来体现其可见性的。
内存屏障分为两种:Load Barrier 和 Store Barrier
Load Barrier可以让用户内存中的数据失效,强制从系统内存加载数据;
Store Barrier让写入用户内存中的共享变量值及时更新写入系统内存,以便让其他线程可见。
这样就保证共享变量的可见性,volatile限制重排序的作用,也是其一大特性,但是这里对有序性不做过多分析,一笔带过。