1、概念
单例模式是为了保证在一个JVM中类的实例只存在一个。这样做的目的是:
- 某些对象的生命周期很短,每次都创建会造成内存的浪费,例如Integer。
- 某些对象,存在第二个实例时属于一种逻辑错误,例如Spring的applicationContext,Servlet的ServletContext等等。
- 某些对象由于它的职责比较单一,存在第二个没有任何意义,例如工厂类。
它的解决方案是:
- 不允许创建其他对象,所以必须是私有的构造器
- 必须存在一个public方法,它的返回值是当前对象的实例。
2、UML图
3、代码
3.1 懒汉式
普通场景下有两种实现方案,第一种实现方案称为懒汉式。
public Class SingletonExample{ private static SingletonExample example; // 私有的构造器 private SingletonExample() { } /** * * @Title: getInstance * @Description: 公共的访问实例对象的方法,必须使用static * @return 实例对象 */ public static SingletonExample getInstance() { if(example == null) { return new SingletonExample(); } return example; } }
3.2 恶汉式
public class SingletonExample { private static SingletonExample example = new SingletonExample(); // 私有的构造器 private SingletonExample(){ } /** * * @Title: getInstance * @Description: 公共的访问实例的方法 * @return 实例对象 */ public static SingletonExample getInstance(){ return example; } }
两种方式比较:懒汉式在定义成员变量时没有进行初始化,在getInstance方法中添加了非NULL判断,而恶汉式在定义成员变量时直接初始化。在getInstance方法中直接返回实例对象。
4、讨论
问题1:如何确保多线程环境下只有一个实例?
答:为了保证在多线程情况下只有一个实例对象,需要使用synchronize关键字进行加锁,第一种实现方式是在getInstance方法上加锁,另外一种实现方式是在Class文件上加锁。在getInstance方法中如果包含其他逻辑代码,会导致整个程序非常缓慢,所以第二种方案拥有更好的性能。
第一种方式,在方法上添加锁。
public static synchronized SingletonExample getInstance(){}
第二种方式,在Class文件上添加锁
public static SingletonExample getInstance() { if(example == null) { synchronized(SingletonExample.class) { return new SingletonExample(); } } return example; }
问题2:如何保证多个类加载器,多个JVM下只有一个实例?
答:当拥有多个类加载器时,每个类加载器拥有该对象的一个实例,不存在多个类加载器拥有同一个实例对象的情形。 多个JVM下必定存在不同的实例。
问题3: 懒汉式与恶汉式有什么区别?
答:从代码中可以看到恶汉式提前创建了对象,如果创建对象的过程很耗性能或者是创建对象需要一些耗时的操作,此时建议使用赖汉式,即需要该对象时在创建。如果对象本身不那么复杂,我们日常启动tomcat时会加载成千上万个类,多创建几个对象也没有多大影响,例如加载工厂类,创建Integer这类型的对象,此时建议使用恶汉式,提前加载。
衡量懒汉与恶汉的区别,以及是否使用sync关键字,都是考虑到创建对象是否会影响性能,如果确定不影响,建议使用恶汉式。会影响,建议使用懒汉式。
5、示例
- 第一类问题,对象生命周期短,反复创建会浪费内存的情形,可以参考Integer类。
- 第二类问题,存在多个实例时是一种逻辑错误,可以参考在一个request请求线程中,出现相同Servlet的多个实例。
- 第三类问题,多个实例没有任何意义,对象的职责比较单一,这类型很常见,例如工厂类。