volatile
简介
java虚拟机提供的轻量级的同步机制
1.保证可见性
添加volatile关键词以后,当线程A改变了a的值,那么其它调用a的值其它线
程,就会得到通知
1 class Mydata{ 2 volatile int a=0; 3 public void resizeA() { 4 this.a=60; 5 } 6 } 7 public class VolatileDemo { 8 public static void main(String[] args) { 9 Mydata mydata = new Mydata(); 10 new Thread(()->{ 11 System.out.println(Thread.currentThread().getName()+"\t com in"); 12 try { 13 TimeUnit.SECONDS.sleep(3);//延迟3秒 14 }catch (InterruptedException e){ 15 e.printStackTrace(); 16 } 17 mydata.resizeA(); 18 System.out.println(Thread.currentThread().getName()+"\t updata number value:"+mydata.a); 19 },"aaa").start(); 20 21 while (mydata.a == 0) { 22 //等待 23 } 24 System.out.println(Thread.currentThread().getName()+"\t over"+mydata.a); 25 } 26 27 }
不加volatile 结果1
aaa com in
aaa updata number value:60
不加volatile 线程main 得到的值一直是a=0,所以一直套在死循环中出不来
加volatile 结果2
aaa com in
aaa updata number value:60
main over60
加了volatile 线程aaa改了a的值,立马刷新到主内存中去,并通知其它线程
2.不保证原子性
当多个线程同时拿到值后,同时返回到主内存时,一个线程占用了主内存,将值写入,其它线程写主内存,挂起,当通知其它线程时,其它线程就会写进来,从而导致数据损失;
1 class Mydata{ 2 volatile int a=0; 3 public void resizeA() { 4 this.a++; 5 } 6 } 7 public class VolatileDemo { 8 public static void main(String[] args) { 9 Mydata mydata = new Mydata(); 10 for (int i = 0; i < 200; i++) { 11 for (int j = 0; j <100 ; j++) { 12 new Thread(()->{ 13 mydata.resizeA(); 14 },"aaa").start(); 15 } 16 } 17 while (Thread.activeCount() > 2) {//还剩2个,main和gc 18 Thread.yield();//让出cpu 19 } 20 System.out.println(Thread.currentThread().getName()+"\t "+mydata.a); 21 } 22 23 }
计算的数值一定要大,很难发生数据损失
3.禁止指令重排
什么是指令重排?
计算机为了提高性能在执行程序是,编译器和处理器会对程序执行指令重排;
指令重排的要求:
-
单线程的情况下不会改变最终的执行结果
-
必须考虑数据的依赖性
案例
1 public class demo{ 2 int a=0; 3 boolean flag=false; 4 public void test(){ 5 a=1;//语句1 6 flag=true;//语句2 7 } 8 //因为没有依赖关系,编译器可能将1和2换位置 9 //多线程时,由于速度太快,先执行2,1还没有执行,就执行了test2里面的方法,导致结果无法预测 10 public void test2(){ 11 if(flag){ 12 a+=5; 13 14 } 15 } 16 }
volatile的应用案例
单例模式
1 public class demo{ 2 int a=0; 3 boolean flag=false; 4 public void test(){ 5 a=1;//语句1 6 flag=true;//语句2 7 } 8 //因为没有依赖关系,编译器可能将1和2换位置 9 //多线程时,由于速度太快,先执行2,1还没有执行,就执行了test2里面的方法,导致结果无法预测 10 public void test2(){ 11 if(flag){ 12 a+=5; 13 14 } 15 } 16 }
synchronized不能禁止指令重排,所以要加volatile来禁止指令重排
因为实例化会发生三个步骤
1.分配内存空间
2.初始化对象
3.指向instance 此时!=null
因为2 3没有数据依赖,所以可能发生指令重排,3先执行,2后,而此时2实际为空对象,所以多线程,在3执行前,使用对象会发生异常;
拓展:不使用线程锁来实现懒汉单例模式同时保证线程安全
1 //静态内部类 2 public class Singleton4 { 3 private Singleton4(){} 4 private static class Inner{ 5 private static final Singleton4 INSTANCE=new Singleton4(); 6 } 7 public static Singleton4 getInstance(){ 8 return Inner.INSTANCE; 9 } 10 }