volatile关键字的作用
多线程下单变量的不可见性
在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后端变量的最新值
public class invisibleDemo01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread , "mythread01").start();
while (true){
if(myThread.isFlag()){
System.out.println("main线程已启动");
}
}
}
}
class MyThread implements Runnable{
private boolean flag = false;
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setFlag(true);
System.out.println("已经修改flag为:"+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
//执行结果:
//已经修改flag为:true
在上面的代码中,myThread线程启动了但是线程睡眠了一秒钟那么main主线程肯定已经执行到了下面的while(true)的死循环代码,但是"main线程已启动"这句话没有打印出来,那么情况只能说是Flag是等于false的。但是明明myThread线程在睡眠过后已经将共享变量的flag的false改为了true,然而flag在main线程中还是false的状态,没有打印语句。
这就是多线程下单变量的不可见性,那为什么会导致这样的情况出现?
首先我们先看一下JMM内的主内存和工作内存他们之间的关系。
共享变量即代码里面的flag在一开始就存放在了主内存
中的堆中
而每一条的线程都有一个独立的工作内存
1.创建的flag=false从myThread线程同步到主内存中
2.然后main线程在主内存得到flag=false
3.在一秒睡眠过后flag=true,此时main已经执行死循环,因为执行速度十分快,快到flag还没读到主内存的最新值,所以在main线程中flag一直等于false,不会打印语句。
解决不可见性的办法有两种:
加锁
public class invisibleDemo01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread , "mythread01").start();
while (true){
synchronized (myThread){
if(myThread.isFlag()){
System.out.println("main线程已启动");
}
}
}
}
}
//执行结果:
//main线程已启动
//main线程已启动
//main线程已启动
//main线程已启动...
在myThread执行完后获得myThread的锁后再执行判断,这时候就会拿到flag的值为true,不断的打印字段
添加关键字volatile
class MyThread implements Runnable{
private volatile boolean flag = false;
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setFlag(true);
System.out.println("已经修改flag为:"+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
工作原理:
1.myThread线程从主内存读取到数据放入对应的工作内存
2.将flag的值改为true,但是这个时候flag的值还没有写入到主内存
3.此时main方法读取到了flag的值为false
4.当myThread线程将flag的值写回去后,失效其他线程对此变量的副本
5.再次堆flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中
总结:volatile保证不同线程堆共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另一个线程立即看到最新的值
非原子性
volatile关键字的添加不能保证线程的原子性,有实现原子性操作需要使用原子类属性或者在程序上加锁。
禁止指令重排
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。重排序分3种类型:
1.编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
2.指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
而禁止指令重排可以避免一些,多线程下的一些极端状态的出现(数据有偏差)
sm,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
3.内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
而禁止指令重排可以避免一些,多线程下的一些极端状态的出现(数据有偏差)