单例模式(Singleton Pattern):确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象建立型模式。java
单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行建立这个实例;三是它必须自行向整个系统提供这个实例。
单例模式是结构最简单的设计模式一,在它的核心结构中只包含一个被称为单例类的特殊类。 数据库
单例模式结构图中只包含一个单例角色:
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户能够访问它的惟一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,做为外部共享的惟一实例。编程
单例模式是一种比较常见的设计模式。设计模式
单例模式做用:安全
1.控制资源的使用,经过线程同步来控制资源的并发访问;多线程
2.控制实例产生的数量,达到节约资源的目的。并发
3.做为通讯媒介使用,也就是数据共享,它能够在不创建直接关联的条件下,让多个不相关的两个线程或者进程之间实现通讯。编程语言
在如下状况下能够考虑使用单例模式:
(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 static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种写法可以在多线程中很好的工做,并且看起来它也具有很好的lazy loading,可是,遗憾的是,效率很低,99%状况下不须要同步。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然致使类装载的缘由有不少种,在单例模式中大多数都是调用getInstance方法, 可是也不能肯定有其余的方式(或者其余的静态方法)致使类装载,这时候初始化instance显然没有达到lazy loading的效果。
public class Singleton { private Singleton 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 final Singleton 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 class Singleton implements java.io.Serializable { public static Singleton 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 static Singleton 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)