单例模式是开发中使用频率极高的一个模式,比如Spring的bean默认就是单例的。
概念
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式有一下特点:
- 单例类只有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
实现方式
一般意义上来讲单例模式有两种实现方式,懒汉模式和饿汉模式。
饿汉模式
1 2 3 4 5 6 7
| public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
|
类加载自动初始化对象,不是lazy init ,系统启动的压力增大。
懒汉模式
1 2 3 4 5 6 7 8 9 10 11
| public class Singleton { private static Singleton instance; private Singleton (){}
public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
典型的懒汉模式,在需要初始化使用单例对象时才决定是否初始化,减轻了启动的负担,但是无法在多线程环境下运行,可能会导致产生多个实例。
改进版的懒汉模式
为了解决懒汉模式在多线程情况的问题,我们使用synchronized关键词同步代码,不就可以避免了同步问题了吗?
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
这种改良方式的确会解决多线程的问题,但同时synchronized会导致每次方法调用时锁住Singleton
实例一次,性能消耗太大。而且instance一旦初始化后,同步锁更加拖累效率。
双重锁定的懒汉模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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; } }
|
这种模式避免了域在被初始化之火访问这个域的锁定开销。这种模式的基本思想是:
第一次检查时看看有没有锁定,看一看这个域是否已被初始化了;第二次检查时有锁定,只有当第二次检查时表明这个域没有被初始化,才开始进行初始化。
这种模式既规避了延迟加载也避免了重复加锁带来的额外性能消耗。
静态内部类
1 2 3 4 5 6 7 8 9
| 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种,做到了延迟加载,也没有引入额外的问题和性能开销。