1.volatile关键字的作用是什么?
a.当一个变量被定义为volatile之后,它将具备两种特性:
一:保证此变量对所有线程的可见性
可见性的意思为:当某个线程对volatile变量的值进行了修改,其他的线程是可以立即得知的;而普通的变量是无法做到这一点的,线程先是对工作内存当中的普通变量副本进行修改,再将变量的值在主内存当中进行刷新,对工作内存修改完成而没来得及同步到主内存中的这段时间内,其他线程将会读到过期的值,造成线程不同步;而volatile定义的变量可以认为工作内存与主内存达成某种协议,工作内存中volatile变量值的改变会被立即刷新到主内存中(稍后还会详细论述),因此被volatile修饰的变量可以认为是具有内存可见性的;(但这并不能完全保证线程安全)
public class Demo {
static volatile int k=0;
class a extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
k++;
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Demo().new a().start();
}
//等待所有的线程结束,再打印出k的值
while(Thread.activeCount()>1){
Thread.yield();
}
System.out.println(k);
}
}
如图所示代码:一共创建了20个线程,每个线程都会对k自加1000次,如果线程同步的话,那么输出的k值应该是20000,但是实际输出的值确永远是小于20000的,线程并不同步。
原因是:在进行变量k++操作的时候,k值的变化是依赖于它本身的,自加操作并不是原子性的操作;K++就是k=k+1;而加法是通过数据结构栈来实现的,执行k=k+1操作,计算机执行的实k1+,是先把变量k push()进栈,再将1 push()进栈,遇到+号,执行k+1,再将k+1的值赋给k,就是说在你将k push()到栈里面,但是操作还未完成的过程的这段时间内,其他线程有可能读取了还未改变的k值,或对k值进行了改变,因而造成线程不同步;
二:禁止指令重排序优化
首先弄清楚什么是指令重排序:cpu采用了允许将多条语句指令不按程序规定的顺序分开发送给各个相应的电路单元处理;当然,并不是随便乱重排的,cpu需要能够正确处理指令点之间的依赖关系,保障程序能得出正确的结果;对指令的分别执行速度必定会大于严格按照指令的顺序,单一电路单元的处理速度更快;
禁止指令重排序:在对Volatile变量赋值的过程中,会加入一个“内存屏障”,不能把这个屏障后面的指令重排序到屏障之前;
那指令重排序是如何干扰程序执行的呢?看下面的示例:
volatile boolean flag=false;
//初始化参数的线程(a)
config con=new config();
(1) con.setXX("XX");
(2) con.setYY("YY");
//再通知其他的线程初始化完毕,可以执行后面的操作
(3) flag=true;
//后续工作的线程,依赖于初始化线程(b)
while(!flag){
Thread.sleep();
}
(4) doSomething(config);
程序的本意是等线程a初始化完毕之后再执行线程b,但是因为指令重排序的存在,指令(3)的执行顺序有可能在指令(1)或者(2)之前就执行完毕了,这样就会导致config对象还没有初识化完成就执行了指令(4)的操作,可能会导致指令(4)无法正常执行,加入Volatile则可以避免这种情况;