一、特点
- 保障可见性:如果变量被 volatile 润饰,那么每次批改之后,接下来在读取这个变量的时候肯定能读取到该变量最新的值。
- 不保障原子性
- 禁止指令重排
二、代码
1.验证volatile的可见性
import java.util.concurrent.TimeUnit;class Share { public volatile int num1 = 0; public int num2 = 0; public void method1() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } this.num1 = 1; } public void method2() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } this.num2 = 1; }}public class Solution { public static void main(String[] args) { Share share = new Share(); //验证volatile的可见性 new Thread(() -> { share.method1(); }).start(); while (share.num1 == 0) { } System.out.println("num1 break loop, num1 = " + share.num1); //验证非volatile的不可见性 new Thread(() -> { share.method2(); }).start(); while (share.num2 == 0) { } System.out.println("num2 break loop, num2 = " + share.num2); }}
- num1用volatile润饰,领有可见性,而num2不领有可见性。证实了volatile能够保障变量的可见性
外汇名词解释https://www.fx61.com/definitions
2.双重测验锁机制的单例模式
class Singleton { private static volatile Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }}
- 双重测验锁机制的单例模式,线程平安且在多线程状况下能放弃高性能。
- 留神Singleton类的动态变量instance应用volatile润饰,用于禁止指令重排
//instance = new Singleton(); 分为上面三步执行(伪代码)memory = allocate;//1.调配对象内存空间initialization(memory);//2.初始化对象instance = memory;//3.设置instance指向刚调配并初始化结束的内存地址,此时instance != null
- 步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行后果在单线程中并没有扭转,因而这种重排优化是容许的。所以指令重排只会保障串行语义的执行的一致性(单线程),但不会关怀多线程间的语义一致性。
memory = allocate;//1.调配对象内存空间instance = memory;//3.设置instance指向刚调配的内存地址,此时instance != null。initialization(memory);//2.初始化对象
- 所以在执行的时候第二步和第三步齐全有可能颠倒程序(伪代码如上),所以当一个线程拜访instance不为null时,因为instance实例未必曾经初始化实现,也就造成了线程平安问题。所以instance变量须要应用volatile关键字润饰,用于禁止指令重排