volatile关键字的特性及证明
volatile是java虚拟机提供的轻量级的同步机制
JMM(Java内存模型)是围绕着并发编程中原子性、可见性、有序性这三个特征来建立的
原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行。
可见性:当多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:程序执行的顺序按照代码的先后顺序执行。
volatile保证了可见性,有序性,不保证原子性
证明可见性的代码:
1 package concurrent; 2 3 import java.util.concurrent.TimeUnit; 4 5 /* 6 * @description: volatile特性 7 * @date 2019.04.22 20:48 8 */ 9 //数据类 10 class Mydata{ 11 12 volatile int num = 0; 13 14 public void changeNum(){ 15 this.num = 100; 16 } 17 } 18 19 public class VolatileDemo { 20 21 public static void main(String[] args) throws InterruptedException{ 22 Mydata mydata = new Mydata(); 23 new Thread(() -> { 24 System.out.println("===="+Thread.currentThread().getName() +"线程启动==="); 25 //暂停3秒 26 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {} 27 //3秒后t1线程改变num的值 28 mydata.changeNum(); 29 System.out.println(Thread.currentThread().getName()+"线程将num的值改为"+mydata.num); 30 },"t1").start(); 31 32 //num的值不变就一直循环 33 long begin = System.currentTimeMillis(); 34 while (mydata.num == 0){ 35 //num如果不被volatile修饰会一直循环 36 } 37 long cost = System.currentTimeMillis() - begin; 38 System.out.printf(Thread.currentThread().getName()+"线程检测到num的值已经改变,cost{%d},证明了volatile的可见性",cost); 39 } 40 }
运行结果为:
====t1线程启动=== t1线程将num的值改为100 main线程检测到num的值已经改变,cost{3001},证明了volatile的可见性
证明不保证原子性的代码:
class Mydata{ volatile int num = 0; public void changeNum(){ this.num = 100; } public void numIncreOne(){ this.num++; } } public class VolatileDemo { public static void main(String[] args) throws InterruptedException{ Mydata mydata = new Mydata(); //开启10个线程每个线程调用1000次num++ for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { mydata.numIncreOne(); } },String.valueOf(i)).start(); } //输出num的值,如果volatile能保证原子性num将等于10000 System.out.println(mydata.num); System.out.println(mydata.num ==10000?"volatile可以保证原子性":"volatile无法保证原子性"); } }
输出结果:
5856
volatile无法保证原子性
多线程环境中,线程交替执行,编译器会通过对指定进行重排序来进行优化。被volatile修饰的变量不会参与重排序,保证有序性。
证明有序性的代码:
1 int num = 0; 2 3 private boolean flag = false; 4 5 private void reSort1(){ 6 num = 1; //语句1 7 flag = true; //语句2 8 } 9 10 private void reSort2(){ 11 if(flag){ 12 num++; 13 System.out.println("num的值为"+num); 14 } 15 }
多线程情况下有可能先执行语句2,再执行语句1,从而导致num只自增1次,输出为1。