多线程--volatile

  在解释volatile关键字之前,先说说java的指令重排以及代码的执行顺序。

  指令重排:

public void sum(){
	int x = 1;
	int y = 2;
int x = x + 1;
int sum = x + y; }

  按代码的顺序,执行时先给x赋值再给y赋值,再执行x=x+1;最后求和。

  由于x=x+1和sum=x+y是写操作不会进行指令重排,但是x=1与y=2是不互斥的;出于性能优化的考虑(对x赋值后直接对x进行操作可以节省重新获取x内存地址的时间),指令重排的最大可能是先对y赋值再对x赋值。

  指令重排不会影响执行结果,是一种优化方式。

  代码的执行顺序:单线程时volatile变量前后的代码不被编译器优化重排,代码顺序执行。多线程的并发情况下,对共享变量的操作顺序。由于多线程是竞争执行的,其执行顺序具有随机性,操作volatile变量时很有可能打破操作变量的原子性。

  1.volatlie不保证对变量进行操作的原子性

  对于基本数据类型的变量如int类型:多线程情况下的操作是从堆内存中读取变量值到线程栈,在线程栈中对变量值进行操作,再将变量值写回堆内存。打破原子性是指两个线程均从堆内存读数据再到写回堆内存的过程中间是可中断的,导致变量的值不可预测。例如:取出x=1;第一个线程进行x++操作,第二个线程进行x--操作,最终执行的结果有可能是0也有可能是2。

public class VolatileNotAtomic {
	private static volatile long count = 0L;
	private static final int NUMBER = 10000;
	
	public static void main(String[] args) {
		Thread substractThread = new SubstractThread();
		substractThread.start();
		
		for (int i = 0; i < NUMBER; i++) {
			//synchronized(VolatileNotAtomic.class) {
				count++;
			//}
		}
		
		//等待线程结束
		while (substractThread.isAlive()) {}
		
		System.out.println("count : " + count);
	}
	
	private static class SubstractThread extends Thread {
		@Override
		public void run() {
			for (int i = 0; i < NUMBER; i++) {
				//synchronized (VolatileNotAtomic.class) {
					count--;
				//}
			}
		}
	}

}

  执行此段代码,运行结果不为0;说明volatile变量的操作不具有原子性。若要保证操作的原子性,打开注释的同步代码块即可。

  volatile的语义仅保证指令不发生重排,保证变量的内存可见性。此处内存指主内存(堆内存)而不是工作内存(栈内存)。线程获取共享变量的值先从主内存空间读值加载到工作内存中,线程对变量值的读写操作后面都是发生工作内存中,并不保证与主内存值同步,此时在多线程并发时会导致变量值不一致。用volitale修饰变量后,强制从主内存读写变量值,保证主内存值的可见性。

  2. volatile保证变量本身读写的原子性,读操作在写操作之后执行;典型应用场景:一写多读

  下面以分析单例双检锁的实现过程来阐述volatile一写多读的应用。

  对象的实例化顺序:

  父类静态成员及静态代码块 ==> 父类静态方法 ==> 子类静态成员及静态代码块 ==>子类静态方法 ==> 父类普通成员及方法 ==> 父类构造方法 ==> 子类普通成员及方法 ==> 子类构造方法

  对象在实例化时首先会向堆内存申请空间,然后执行上述实例化过程。

  单例双检锁的实现如下:

public class Singleton {
	private static Singleton singleton = null;	
	public static Singleton getInstance() {
		if (null == singleton) {
			synchronized(Singleton.class){
				if (null == singleton) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
		
	}
}

  如上代码的问题在于:多线程执行时,如果线程1以执行到new Singleton(),但是对象实例化过程并未完成,此时线程2调用getInsatnce会获取到未完成实例化的对象(对象已分配内存可以被访问),导致结果错误。

  解决上述问题只需在sinleton变量前加上volatile关键字即可。

public class Singleton {
	private static volatile Singleton singleton = null;	
	public static Singleton getInstance() {
		if (null == singleton) {
			synchronized(Singleton.class){
				if (null == singleton) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
		
	}
}

 

posted @ 2019-09-02 10:14  北辰Root  阅读(240)  评论(0编辑  收藏  举报