单例模式是设计模式中使用很频繁的一种模式,在各种开源框架、应用系统中多有应用。单例模式又叫做单态模式或者单件模式。在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格式的对象串行化方式。这种方式也存在上述的问题,所以在使用的时候要格外小心。
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