作用
- 可见性
- 有序性
内存语义
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中,当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。
底层实现
内存屏障
使用场景
-
普通变量赋值可以,但是含复合运算的赋值不行(类似i++),也不能修饰引用类型,不能保证对象内部的属性可见性。
volatile int a = 10; // 可以 volatile DeadlockDemo demo = new DeadlockDemo(); // 不可以
-
状态标志,判断业务是否结束
public class VolatileDemo { private static volatile boolean flag = true; public static void main(String[] args) throws InterruptedException { new Thread(()->{ while (flag){ System.out.println("flag的值为:"+flag); } },"t1").start(); new Thread(()->{ flag = false; System.out.println("另一个线程更改flag为false了"); },"t2").start(); }
-
开销较低的读,写锁策略
/**
* 使用:当读远多于写,结合使用内部锁和 volatile 变量来减少同步的开销
* 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性
*/
public class Counter {
private volatile int value;
public int getValue() {
return value; //利用volatile保证读取操作的可见性
}
public synchronized int increment() {
return value++; //利用synchronized保证复合操作的原子性
}
}
-
双端锁的单例模式
public class SingletonDemo { // volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取) private volatile SingletonDemo instance = null; private SingletonDemo() { //构造器必须私有 不然直接new就可以创建 } public SingletonDemo getInstance() { //第一次判断,假设会有好多线程,如果 SingletonDemo 没有被实例化,那么就会到下一步获取锁,只有一个能获取到, //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程 if (instance == null) { synchronized (SingletonDemo.class) { //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断 instance 是null,他就实例化了instance,然后他出了锁, //这时候线程B经过等待A释放的锁,B获取锁了,如果没有第二个判断,那么他还是会去new SingletonDemo(),再创建一个实例,所以为了防止这种情况,需要第二次判断 if (instance == null) { instance = new SingletonDemo(); } } } return instance; } }
扩展:除了双端锁的单例模式,还有一种安全的单例模式就是静态内部类
//现在比较好的做法就是采用静态内部内的方式实现 class SingletonDemo2 { private SingletonDemo2() { } private static class SingletonDemoHandler { private static SingletonDemo2 instance = new SingletonDemo2(); } public static SingletonDemo2 getInstance() { return SingletonDemoHandler.instance; } }
-
对象的属性修改原子类(AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater)
更新的对象属性必须使用 public volatile 修饰符。
使用目的:以一种线程安全的方式操作非线程安全对象内的某些字段