[从零开始学设计模式-java]单例模式(Singleton)

单例模式是设计模式中使用很频繁的一种模式,在各种开源框架、应用系统中多有应用。单例模式又叫做单态模式或者单件模式。在GOF书中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式的目的就是要控制特定的类只产生一个对象,当然也允许在一定情况下灵活的改变对象的个数。那么怎么来实现单例模式呢?一个类的对象的产生是由类构造函数来完成的,如果想限制对象的产生,一个办法就是将构造函数变为私有的(至少是受保护的),使得外面的类不能通过引用来产生对象;同时为了保证类的可用性,就必须提供一个自己的对象以及访问这个对象的静态方法。

单例模式可分为有状态的和无状态的。有状态的单例对象一般也是可变的单例对象,多个单态对象在一起就可以作为一个状态仓库一样向外提供服务。没有状态的单例对象也就是不变单例对象,仅用做提供工具函数。


代码实例

单例模式代码实现版本较多,可以按照是否延迟加载、是否多线程安全大致分为以下几类。

1,懒汉式,多线程不安全

/**
 * 懒汉式:线程不安全
 * 是否Lazy初始化:是
 * 是否多线程安全:否
 * @author GIGI
 *
 */
public class Singleton1 {
	private static Singleton1 instance = null;
	
	private Singleton1(){}
	
	public static Singleton1 getInstance(){
		if(instance == null){
			instance = new Singleton1();
		}
		return instance;
	}
}
2,懒汉式,线程安全

/**
 * 懒汉式:线程安全
 * 是否Lazy初始化:是
 * 是否多线程安全:是
 * @author GIGI
 *
 */
public class Singleton2 {
	private static Singleton2 instance = null;
	
	private Singleton2(){}
	
	public static synchronized Singleton2 getInstance(){
		if(instance == null){
			instance = new Singleton2();
		}
		return instance;
	}
}
3,饿汉式
/**
 * 饿汉式:线程安全
 * 是否Lazy初始化:否
 * 是否多线程安全:是
 * @author GIGI
 *
 */
public class Singleton3 {
	private static Singleton3 instance = new Singleton3();;
	
	private Singleton3(){}
	
	public static Singleton3 getInstance(){
		return instance;
	}
}
4,双检锁/双重校验锁
/**
 * 双检锁/双重校验锁
 * 是否Lazy初始化:否
 * 是否多线程安全:是
 * @author GIGI
 *
 */
public class Singleton4 {
	private volatile static Singleton4 instance = null;
	
	private Singleton4(){}
	
	public static Singleton4 getInstance(){
		if(instance == null){
			synchronized(Singleton4.class){
				if(instance == null){
					instance = new Singleton4();
				}
			}
		}
		return instance;
	}
}
5,登记式,静态内部类
/**
 * 登记式/静态内部类
 * 是否Lazy初始化:是
 * 是否多线程安全:是
 * @author GIGI
 *
 */
public class Singleton5 {
	private static class SingletonHolder{
		private static final Singleton5 INSTANCE = new Singleton5();
	}
	
	private Singleton5(){}
	
	public static final Singleton5 getInstance(){
		return SingletonHolder.INSTANCE;
	}
}
6,枚举类
/**
 * 枚举类
 * @author GIGI
 *
 */
public enum Singleton6 {
	INSTANCE;
	
	public void anyMethod(){}
}


这六种单例模式实现,枚举类是 实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。


单例模式邪恶论:
    看这题目也许有点夸张,不过这对初学者是一个很好的警告。单例模式在java中的使用存在很多陷阱和假象,这使得没有意识到单例模式使用局限性的你在系统中布下了隐患……
    其实这个问题早在2001年的时候就有人在网上系统的提出来过,我在这里只是老生常谈了。但是对于大多的初学者来说,可能这样的观点在还很陌生。下面我就一一列举出单例模式在java中存在的陷阱。


多个虚拟机

    当系统中的单例类被拷贝运行在多个虚拟机下的时候,在每一个虚拟机下都可以创建一个实例对象。在使用了EJB、JINI、RMI技术的分布式系统中,由于中间件屏蔽掉了分布式系统在物理上的差异,所以对你来说,想知道具体哪个虚拟机下运行着哪个单例对象是很困难的。
    因此,在使用以上分布技术的系统中,应该避免使用存在状态的单例模式,因为一个有状态的单例类,在不同虚拟机上,各个单例对象保存的状态很可能是不一样的,问题也就随之产生。而且在EJB中不要使用单例模式来控制访问资源,因为这是由EJB容器来负责的。在其它的分布式系统中,当每一个虚拟机中的资源是不同的时候,可以考虑使用单例模式来进行管理。


多个类加载器
    当存在多个类加载器加载类的时候,即使它们加载的是相同包名,相同类名甚至每个字节都完全相同的类,也会被区别对待的。因为不同的类加载器会使用不同的命名空间(namespace)来区分同一个类。因此,单例类在多加载器的环境下会产生多个单例对象。
    也许你认为出现多个类加载器的情况并不是很多。其实多个类加载器存在的情况并不少见。在很多J2EE服务器上允许存在多个servlet引擎,而每个引擎是采用不同的类加载器的;浏览器中applet小程序通过网络加载类的时候,由于安全因素,采用的是特殊的类加载器,等等。
    这种情况下,由状态的单例模式也会给系统带来隐患。因此除非系统由协调机制,在一般情况下不要使用存在状态的单例模式。

错误的同步处理
    在使用上面介绍的懒汉式单例模式时,同步处理的恰当与否也是至关重要的。不然可能会达不到得到单个对象的效果,还可能引发死锁等错误。因此在使用懒汉式单例模式时一定要对同步有所了解。不过使用饿汉式单例模式就可以避免这个问题。

子类破坏了对象控制
    在上一节介绍最后一种扩展性较好的单例模式实现方式的时候,就提到,由于类构造函数变得不再私有,就有可能失去对对象的控制。这种情况只能通过良好的文档来规范。

串行化(可序列化)
    为了使一个单例类变成可串行化的,仅仅在声明中添加“implements Serializable”是不够的。因为一个串行化的对象在每次返串行化的时候,都会创建一个新的对象,而不仅仅是一个对原有对象的引用。为了防止这种情况,可以在单例类中加入readResolve方法。关于这个方法的具体情况请参考《Effective Java》一书第57条建议。

    其实对象的串行化并不仅局限于上述方式,还存在基于XML格式的对象串行化方式。这种方式也存在上述的问题,所以在使用的时候要格外小心。


Java中关于单例模式的10个面试问题

1) 哪些类是单例模式的后续类?在Java中哪些类会成为单例?

2)你能在Java中编写单例里的getInstance()的代码?

3)在getInstance()方法上同步有优势还是仅同步必要的块更优优势?你更喜欢哪个方式?

4)什么是单例模式的延迟加载或早期加载?你如何实现它?

5) Java平台中的单例模式的实例有哪些?

6) 单例模式的两次检查锁是什么?

7)你如何阻止使用clone()方法创建单例实例的另一个实例?

8)如果阻止通过使用反射来创建单例类的另一个实例?

9)如果阻止通过使用序列化来创建单例类的另一个实例?

10) Java中的单例模式什么时候是非单例?

链接:http://blog.csdn.net/hintcnuie/article/details/17968261


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值