欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

volatile有什么用?为什么volatile变量的复合操作不具有安全性?volatile是怎么实现可见性和禁止重排序的(有序性)?具体的禁止重排优化实例?

程序员文章站 2022-05-28 18:01:27
...

volatile有什么作用?

  1. volatile修饰的变量能够保证可见性,volatile修饰的变量被修改后将会被强制刷新到主内存中,当某个线程读取volatile修饰的变量的时候,会将保存在该线程工作内存的变量副本清空,强制从主内存中读取该变量新的值
  2. volatile禁止重排序优化,volatile修饰的变量的写操作总是发生在对volatile变量的读操作之前

volatile变量的复合操作为什么不具有安全性?

public class VolatileVisibility{
	public static volatile int i=0;   // volatile修饰的变量
	public static void increase(){
		i++; // volatile修饰的变量的复合操作
	}
}

需要注意的是volatile修饰的变量的可见性指的是volatile变量的写操作仅仅指的是单个的写操作而不是对volatile变量的复合操作,复合操作不具有安全性(因为复合操作是分两步完成的,线程可以在两步中间访问,不具有原子性

这种情况下要保证线程安全,increase()方法需要使用synchronized进行修饰,synchronzed具有原子性和可见性,因此不需要使用volatile对变量进行修饰保证可见性

volatile如何实现禁止重排序

volatile禁止指令重排序,避免多线程环境下程序出现乱序执行的现象(乱序执行影响可见性和有序性)

  1. 一方面happpen-before原则中的volatile原则表明volatile修饰的变量的写操作总是发生在volatile修饰的变量的读操作之前,保证了有序性
  2. 另一方面因为编译器和处理器会进行指令重排序,通过在指令序列中插入内存屏障指令禁止在内存屏障指令前后的指令执行重排序优化。(等同于编译器重排序规则和
    处理器重排序规则),内存屏障指令的另一个作用是强制将cpu缓存中的数据刷新到主内存中,保证其他cpu读到的数据都是最新的版本,保证了可见性。

总结:volatile具有的可见性和有序性实际上是通过一部分的happen-before规则中的volatile规则,一部分是通过在执行序列中插入的内存屏障序列完成的。

volatile禁止指令重排序的实例

public class DoubleCheckLock{
	private static DoubleCheckLock instance;
	private DoubleCheckeLock(){
	}
	public static DoubleCheckLock getInstance(){
		if(instance==null){
			synchronzied(DoubleCheckLock.class){
				if(instance==null){
					instacnce=new DoubleCheckLock();
				}
			}
		}
	}
}

这就是一个单例双重检测的代码,这段代码在单线程环境下没有什么问题
但是在多线程中可能就出现问题,**因为某个线程执行到第一次检测的时候,读取到的instance不为null时候,instance的引用对象可能没有完成初始化,**因为instance=new DoubleCheckLock()可以分为3步完成

分配内存空间    memory = allocate()
初始化对象        instance(memory)
设设置引用变量指向对象的地址     instance = memory

但是由于初始化对象和设置执行内存地址可能会重排序

分配内存空间    memory = allocate()
设设置引用变量指向对象的地址     instance = memory
初始化对象        instance(memory)

因为初始化对象和设置引用变量指向内存地址之间没有数据依赖性,在单线程中重排和不重排并不影响结果,因此这种指令重排序是允许的(指令重排对单线程来说能够保证语义一致性,但是对多线程的语义一致不能保证),因此,带来问题,当一条线程访问instance不为null的时候,此时instance实例不一定已经初始化完成了,就造成了线程安全问题

此时,使用volatile禁止instance变量被执行指令重排序 private volatile static DoubleCheckLock instance;

**总结:**双重检测的单例,在单线程情况下允许指令重排序,指令重排序也会保证内存语义一致,但是在多线程下不能保证内存语义一致性,因为创建一个对象实例的时候会经过三个步骤,一条线程访问instance不为null并不能说明该实例已经被初始化完成了,使用volatile修饰声明的引用变量,禁止指令重排优化