在以下情况下可以考虑使用单例模式: (1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。 (2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
主要优点:
A. 提供了对唯一实例的受控访问。
B. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
C. 允许可变数目的实例。
主要缺点:
A. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
B. 单例类的职责过重,在一定程度上违背了 "单一职责原则"。
C. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
- public class Singleton {
- private static Singleton instance;
- private Singleton (){}
- public static Singleton getInstance() {
- if(instance ==null) {
- instance =new Singleton();
- }
- return instance;
- }
- }
这种写法 lazy loading 很明显,但是致命的是在多线程不能正常工作。
- public class Singleton {
- private staticSingleton instance;
- privateSingleton (){}
- public static synchronized Singleton getInstance() {
- if(instance ==null) {
- instance =new Singleton();
- }
- return instance;
- }
- }
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的 lazy loading,但是,遗憾的是,效率很低,99% 情况下不需要同步。
- public class Singleton {
- private staticSingleton instance =new Singleton();
- private Singleton (){}
- public static Singleton getInstance() {
- return instance;
- }
- }
这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
- public class Singleton {
- privateSingleton instance =null;
- static {
- instance =new Singleton();
- }
- private Singleton (){}
- public static Singleton getInstance() {
- return this.instance;
- }
- }
表面上看起来差别挺大,其实和第三种方式差不多,都是在类初始化即实例化 instance。
- public class Singleton {
- private static class SingletonHolder {
- private static finalSingleton INSTANCE =new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return SingletonHolder.INSTANCE;
- }
- }
这种方式同样利用了 classloder 的机制来保证初始化 instance 时只有一个线程,它跟第三种和第四种方式不同的是(很细微的差别):第三种和第四种方式是只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,我想让他延迟加载,另外一方面,我不希望在 Singleton 类加载时就实例化,因为我不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第三和第四种方式就显得很合理。
- public enum Singleton {
- INSTANCE;
- public void whateverMethod() {
- }
- }
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于 1.5 中才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
- public class Singleton {
- private volatile static Singleton singleton;
- private Singleton (){}
- public static Singleton getSingleton() {
- if(singleton ==null) {
- synchronized(Singleton.class) {
- if(singleton ==null) {
- singleton =new Singleton();
- }
- }
- }
- return singleton;
- }
- }
在 JDK1.5 之后,双重检查锁定才能够正常达到单例效果。
有两个问题需要注意:
1. 如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些 servlet 容器对每个 servlet 使用完全不同的类装载器,这样的话如果有两个 servlet 访问一个单例类,它们就都会有各自的实例。
问题修复的办法是:
- private static Class getClass(String classname) throws ClassNotFoundException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- if(classLoader ==null)
- classLoader = Singleton.class.getClassLoader();
- return (classLoader.loadClass(classname));
- }
- }
2. 如果 Singleton 实现了 java.io.Serializable 接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
问题修复的办法是:
- public classSingletonimplements java.io.Serializable {
- public staticSingleton INSTANCE =new Singleton();
- protected Singleton() {
- }
- private Object readResolve() {
- return INSTANCE;
- }
- }
第三种和第五种方式,简单易懂,而且在 JVM 层实现了线程安全(如果不是多个类加载器环境),一般的情况下,我会使用第三种方式,只有在要明确实现 lazy loading 效果时才会使用第五种方式,另外,如果涉及到反序列化创建对象时,可以试着使用枚举的方式来实现单例,不过,我一直会保证我的程序是线程安全的,而且我永远不会使用第一种和第二种方式,如果有其他特殊的需求,我可能会使用第七种方式,毕竟,JDK1.5 已经没有双重检查锁定的问题了。
不过一般来说,第一种不算单例,第四种和第三种就是一种,如果算的话,第五种也可以分开写了。所以说,一般单例都是五种写法。懒汉,恶汉,双重校验锁,枚举和静态内部类。
饿汉式单例类与懒汉式单例类比较 饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。 懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。
新型单例实现方法 饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!下面我们来学习这种更好的被称之为 Initialization on Demand Holder (IoDH)的技术。 在 IoDH 中,我们在单例类中增加一个静态 (static) 内部类,在该内部类中创建单例对象,再将该单例对象通过 getInstance()方法返回给外部使用,实现代码如下所示:
- //Initialization on Demand Holder
- public class Singleton{
- private Singleton(){
- }
- private static class HolderClass{
- private final staticSingleton instance =new Singleton();
- }
- public static Singleton getInstance(){
- return HolderClass.instance;
- }
- public static void main(String args[]){
- Singleton s1, s2;
- s1 = Singleton.getInstance();
- s2 = Singleton.getInstance();
- System.out.println(s1==s2);
- }
- }
编译并运行上述代码,运行结果为:true,即创建的单例对象 s1 和 s2 为同一对象。由于静态单例对象没有作为 Singleton 的成员变量直接实例化,因此类加载时不会实例化 Singleton,第一次调用 getInstance() 时将加载内部类 HolderClass,在该内部类中定义了一个 static 类型的变量 instance,此时会首先初始化这个成员变量,由 Java 虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于 getInstance() 方法没有任何线程锁定,因此其性能不会造成任何影响。 通过使用 IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的 Java 语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持 IoDH)
感谢:http://cantellow.iteye.com/blog/838473
http://blog.csdn.net/iblade/article/details/51107308
来源: http://www.cnblogs.com/kuoAT/p/6725808.html