`

设计模式之单例模式(Singleton)

阅读更多

单例模式:确保一个类只有一个实例,并提供一个全局访问点。

 

要想保证一个类只有一个实例,我们不能将构造方法暴露出去,否则调用方就可能通过你提供的构造方法去实例化该类的实例,这样我们就无法保证该类只有一个实例了。因此,我们不能给类的构造方法赋予public的访问权限。

 

单例模式的实例化分为两种:急切实例化和延迟实例化

 

急切实例化:依赖JVM在加载这个类时马上创建此唯一的单件实例,通常表现为一个静态引用。如果程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,我们可以采取急切实例化的方式创建单件。

public class Product {
	//静态成员——将在JVM加载该类的时候就完成对象的实例化
	private static Product product = new Product();
	
	private Product(){
	}
	
	public static Product newInstance(){
		//始终返回被加载时创建的静态成员实例
		return product;
	}
}
 

急切实例化单例模式UML图:


延迟实例化:只在真正需要该类的实例的时候才实例化。通常在创建和运行时负担比较重的时候选用此种方案。

public class Product {
	//静态成员——之前先不去实例化对象
	private static Product product;
	
	private Product(){
	}
	
	public static Product newInstance(){
		//只有在需要该类的时候调用此方法时才去完成实例化
		if(null == product){
			product = new Product();
		}
		return product;
	}
}
 

延迟实例化单例模式UML图:


延迟实例化所面临的线程安全问题:

很多人在使用延迟实例化单例模式时都没有考虑线程安全问题。我们看下面一段代码:

/**
 * 延迟实例化单例模式
 */
public class Product {
	
	private static Product product;
	
	private Product(){
	}
	
	public static Product newInstance() throws Throwable{
		if(null == product){
			Thread.sleep(5000);//这时可能会有两个线程同时进入到这里
			product = new Product();//之后可能会创建两个Product类的实例
		}
		return product;
	}
}

 上段代码有时也可能只会创建一个实例,这取决哪个线程抢占到执行权,但不排除创建两个实例的可能性,有兴趣的朋友可以自己多尝试几次,或者借助调试的手段手动切换两个线程的执行顺序,就会出现创建两个实例的情景。既然如此,我们以后就不要像上面的代码这样使用延时实例化。

 

如何解决 延迟实例化所面临的线程安全问题:

方法一:使用synchronized,如下所示:

/**
 * 延迟实例化单例模式
 */
public class Product {
	
	private static Product product;
	
	private Product(){
	}
	
	public static synchronized Product newInstance() throws Throwable{
		if(null == product){
			product = new Product();
		}
		return product;
	}
}
 

上面的代码虽然能解决线程安全问题,但是每次调用newInstance方法时都会被同步,无疑会带来性能损耗,你必须知道同步一个方法可能造成程序执行效率下降100倍。如果你可以接受这样的额外损耗,你大可可以这样来用(即简单又有效),如果你可能需要频繁的调用这个同步方法,又无法接受这样的性能损失,可能就得想其他的办法啦。

 

方法二:使用“双重检查加锁”,如下所示:

/**
 * 延迟实例化单例模式
 */
public class Product {
	
	private volatile static Product product;//使用volatile
	
	private Product(){
	}
	
	public static Product newInstance() throws Throwable{
		if(null == product){
			synchronized(Product.class){//保护起来
				if(null == product){//之后再次检查
					product = new Product();
				}
			}
		}
		return product;
	}
}
 

这样做比直接使用同步方法带来的损耗要低很多。如果你不想使用synchronized,也可以使用阻塞队列,JDK也是这么推荐的。

 

方法三:使用急切实例化,当然之前介绍了急切实例化也有他的缺点,如果你能接受的话,这也是个简单有效的方案。

 

单例模式请注意多个ClassLoder

每个类加载器都定义了一个名空间,如果有两个以上的类加载器,不同的类加载器可能会加载同一个类,从整个程序来看,同一个类被加载多次,如果这样的事情发生在单件上,就会产生多个实例并存的“单件”,所以需要引起注意。

 

参考资料:

Head First 设计模式 (中国电力出版社)

 

 

 

 

 

 

 

 

  • 大小: 3.5 KB
  • 大小: 3.2 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics