设计模式拾贝——线程安全的单例设计模式:Doublecheck

在学习设计模式的时候,发现有这样一个十分巧妙的设计方式,虽然看起来并不复杂,但是有很多细节值得注意:

先来看一下代码,稍后我们来解释这个设计的巧妙性:

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

这个代码的巧妙之处在哪里?我们来一点点分析。

首先看代码逻辑:我们希望在用到Myclass这个类的时候,当我们需要一个Myclass类的实例,我们就new一个这样的实例,并且自始至终使用同一个实例。
我们希望只有在需要用到这个实例之前才去new它(为了节省空间和资源)
但是,为了满足这个要求,很多人会想到这样的写法:

class Myclass {
	private static Myclass instance;
	
	private Myclass() {};
	
	public staticMyclass getInstance() {
		if(instance == null) {
					instance = new Myclass();
			}
		}
		return instance;
	}
	
}

这样想法很美好,然而这种设计是线程不安全的:很好理解,假设有若干个线程同时进入了instance==null这个判断中,那么每个线程都会根据这个判断来new一堆实例出来,很明显这样和我们的代码逻辑相悖。

为了更好的理解代码逻辑:想象这样一种情形:一个将军需要让一群炮手去攻击一个目标,并且每个炮手配备一个哨兵,来观测目标是否被摧毁。
我们的目标是:节省哨兵间通信耗费的成本,并且避免重复打击。

按这样的想法:这种设计方案等于让哨兵根据自己看到的情况通报给炮手,很容易导致多个同时观测到目标未被摧毁的哨兵让多个炮手开炮,显然不可取。

另外一种想法是:

class Myclass {
	private static Myclass instance;
	
	private Myclass() {};
	
	public synchronized staticMyclass getInstance() {
		if(instance == null) {
					instance = new Myclass();
			}
		}
		return instance;
	}
}

这样也不行:每次都要进行同步,极其耗费资源,等于每次都需要哨兵之间沟通情报,效率奇差,得不偿失。

还有一种方案:

class Myclass {
	private static Myclass instance;
	
	private Myclass() {};
	
	public staticMyclass getInstance() {
		if(instance == null) {
				synchronized(Myclass.class) {
							instance = new Myclass();
					}
				}
				return instance;
			}
	}
}

这种方法看似很有道理,实际上是大错特错:这里的同步是在判断后进行的,不仅不能满足算法逻辑,还可能浪费大量的资源来同步,错上加错。

那么,我们要介绍的设计方案:doublecheck的好处有哪些呢?

class Myclass {

//首先,让instance之间彼此可见:保证每次判断null的时候能够正确地确定instance的状况
//所以我们使用volatile修饰
	private static volatile Myclass instance;
	
	private Myclass() {};
	
	public static synchronized Myclass getInstance() {
	//第一次判断:先把所有可能去new这个实例的线程放进来
		if(instance == null) {
		//把这些线程放进来之后,再让其中一个线程去new这个实例,排着队等着的其他线程便不能再new这个实例了。
			synchronized(Myclass.class) {
				if(instance == null) {
					instance = new Myclass();
				}
			}
		}
		return instance;
	}
	
}

总结一下这种设计思想:

首先,线程不安全的问题来自于多个线程可能都观测到实例为null这个情况,并且将这些候选线程进行同步,选择其中一个让它先new出这个线程,让剩下的线程打道回府。
整个程序无论调用这个方法多少次,只需要花费第一次同步的代价,而后面需要调用的时候,直接通过null判断,无需任何的同步。

按照之前的比喻,方案就像:让所有的哨兵能够同时观测到目标的情况,在需要攻击时,首先让观测到目标的哨兵之间进行沟通,决定其中一个炮手开炮。之后的所有哨兵都观测到目标被摧毁,故也不会开炮。

这种设计方案十分高明:既满足了线程安全的要求,又满足了资源的利用高效率。

你可能感兴趣的:(java,设计模式,多线程)