定义
由于某种需要,要保证一个类在程序的生命周期当中只有一个实例,并且提供该实例的全局访问方法。
结构
一般包含三个要素:
1、私有的静态的实例对象 private static instance
2、私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例) private Singleton(){}
3、公有的、静态的、访问该实例对象的方法 public static Singleton getInstance(){}
应用场景
单例模式在程序设计中非常的常见,一般来说,某些类,我们希望在程序运行期间有且只有一个实例,原因可能是该类的创建需要消耗系统过多的资源、花费很多的时间,或者业务上客观就要求了只能有一个实例。一个场景就是:我们的应用程序有一些配置文件,我们希望只在系统启动的时候读取这些配置文件,并将这些配置保存在内存中,以后在程序中使用这些配置文件信息的时候不必再重新读取。
分类
单例模式就实例的创建时机来划分可分为:懒汉式
与饿汉式
两种。
懒汉式
:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
顾名思义懒汉式就是应用刚启动的时候,并不创建实例,当外部调用该类的实例或者该类实例方法的时候,才创建该类的实例。可以节省系统资源,体现了延迟加载的思想。是以时间换空间。
懒汉式的缺点
:由于系统刚启动时且未被外部调用时,实例没有创建;如果一时间有多个线程同时调用LazySingleton.getLazyInstance()方法很有可能会产生多个实例。也就是说下面的懒汉式在多线程下,是不能保持单例实例的唯一性的,要想保证多线程下的单例实例的唯一性得用同步,同步会导致多线程下由于争夺锁资源,运行效率不高。
饿汉式
:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
顾名思义懒汉式就是应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。写法简单,在多线程下也能保证单例实例的唯一性,不用同步,运行效率高。以空间换时间。
饥汉式的缺点
:在外部没有使用到该类的时候,该类的实例就创建了,若该类实例的创建比较消耗系统资源,并且外部一直没有调用该实例,那么这部分的系统资源的消耗是没有意义的。
改进版的懒汉模式
:
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为了解决懒汉模式在多线程情况的问题,我们使用synchronized关键词同步代码,可以避免了同步问题。这种改良方式的确会解决多线程的问题,但同时synchronized会导致每次方法调用时锁住Singleton实例一次,性能消耗太大。而且instance一旦初始化后,同步锁更加拖累效率。
双重锁定的懒汉模式
:
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;
}
}
这种模式避免了域在被初始化之火访问这个域的锁定开销。这种模式的基本思想是:
第一次检查时看看有没有锁定,看一看这个域是否已被初始化了;第二次检查时有锁定,只有当第二次检查时表明这个域没有被初始化,才开始进行初始化。
这种模式既规避了延迟加载也避免了重复加锁带来的额外性能消耗。
静态内部类
:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
当getInstance方法第一次调用时,第一次读取SingletonHolder.INSTANCE,导致SingletonHolder类得到初始化,这种方式并没有使用任何线程同步的机制,就保证了一次访问域代码。延迟初始化没有增加访问成本,这个方法值得推荐
总结
5种方式,相对比较推荐第4种和第5种,做到了延迟加载,也没有引入额外的问题和性能开销。