006 volatile的禁止指令重排序特性解决单例的双重校验锁(DCL)的懒汉模式的问题

1、首先看下双重校验锁的懒汉模式来实现的单例(多线程时有问题的)

public class Singleton {

	private static Singleton instance;
	private String name;

	private Singleton() {
		this.name = "我是name属性的值";
	}

	public static Singleton getInstance() {
		if (instance == null) {                // 1
			synchronized (Singleton.class) {   // 2
				if (instance == null) {        // 3
					instance = new Singleton();// 4
				}
			}
		}

		return instance;                       // 5
	}

	public String getName() {                  // 6
		return name;
	}
}

以上的这个demo为例

场景一: 有两个线程, 线程A, 和线程B, 线程A是初次访问getInstance()方法, 此时在代码//1 处instance == null为true, 进入同步代码块, 此时线程B也来访问getInstance()方法, 线程B在代码//1 处instance == null可能返回true(线程A还没有实例化完)然后线程B进入阻塞状态,等线程B拿到锁进入同步代码块的时候instance已经实例化完并且instance最新的值已经刷新回主存(因为同步代码块的unlock之前会把线程中的最新状态刷回主存)所以线程B在代码//3处会返回false, 不会再创建新的实例, 从而保证了单例模式. 也就是说这种情况下是不存在问题的.

 场景二: 同样有两个线程,线程A和线程B, 线程A是初次访问getInstance()方法, 此时在代码//1 处instance == null为true, 进入同步代码块, 此时线程B也来访问getInstance()方法, 线程B在代码//1 处instance == null可能返回false, 此时instance已经完全初始化完成, 那么线程B直接返回初始化好的instance实例. 这种情况下也是没有问题的,能够按照DCL预期的效果实现单例.

场景三: 同样有两个线程,线程A和线程B, 线程A是初次访问getInstance()方法, 此时在代码//1 处instance == null为true, 进入同步代码块, 此时线程B也来访问getInstance()方法, 线程B在代码//1 处instance == null可能返回false, 但是此时的instance并没有完全实例化完, 这样线程B返回了一个没有被完全实例化完的instance, 那么线程B在拿这个instance进行调用代码//6的时候就并不能按照预期拿到 name="我是name属性的值" 的结果,此时DCL的问题就出来了.

关于场景一和场景二不在多做解释,相信大家都能理解, DCL之所以出现问题就是因为场景三的存在, 虽然场景三出现个概率极小, 但是再小的概率在大量的访问下也是会被触发的.

那么为什么会存在场景三呢?

需要知道的是instance = new Singleton();这句代码并不是一个原子操作,他的操作大体上可以被拆分为三步

1.分配内存空间

2.实例化对象instance

3.把instance引用指向已分配的内存空间,此时instance有了内存地址,不再为null了

java是允许对指令进行重排序, 那么以上的三步的执行顺序就有可能是1-3-2. 在这种情况下, 如果线程A执行完1-3之后被阻塞了, 而恰好此时线程B进来了 此时的instance已经不为空了所以线程B走完代码//1以后就直接返回了这个还没有实例化好的instance, 所以在调用其后续的实例方法时就会得不到预期的结果

解决的办法是:


	// private static Singleton instance;
	
	private static volatile Singleton instance;

既然问题找到了, 那么为什么volatile可以避免这个问题呢?
关于volatile的相关解释说明我就不在做赘述了, 有很多相关的介绍文章,大家可以自行学习一下.

如果你已经了解了volatile以后你会知道, volatile是会保证被修饰的变量的可见性 和 有序性

volatile的有序性做一下简单的介绍, 被volatile修饰的变量不参与指令重排(从而保证了单例不会出问题), 在操作volatile变量时 在变量操作之前的代码一定是执行完毕并且是可见的, 在变量操作之后的代码一定是还没有被执行的

所以当instance被定义为 private volatile static Singleton instance的话就会保证在创建对象的时候的执行顺序一定是1-2-3的步骤, 从而保证了instance要么为null 要么是已经完全初始化好的对象, 从而避免了场景三的情况出现.

你可能感兴趣的:(04Java多线程高并发,单例模式,java,开发语言,双重校验锁,volatile的应用)