定义:
Ensure a class has only one instance,and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供整个单例)
单例模式的通用类图如下
editByWpp.png
单例模式的2种常见表现形式
- 1、饿汉式
/**
* @author wpp25
* @date 2019/8/25 9:44
* 描述:饿汉式单例
*/
public class HungerSingleton
private HungerSingleton(){}
private static final HungerSingleton hungerSingleton= new HungerSingleton();
public static HungerSingleton newInstance(){
return hungerSingleton;
}
- 2、懒汉式
/**
* @author wpp25
* @date 2019/8/25 9:43
* 描述: 懒汉式单例
*/
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton lazySingleton = null;
public static LazySingleton newInstance(){
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
单例模式的优缺点
优点
- 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建、销毁;而且创建和销毁又无法优化,单例模式的优势就非常明显了
缺点
- 单例模式一般没有接口,扩展比较困难,若要扩展,除了修改代码基本没有第二条途径可以实现。
- 单例模式和单一职责原则有冲突,一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例把这“要单例”和业务逻辑融合在了一个类中
单例模式的注意事项
首先,在高并发的情况下,请注意单例模式的线程同步情况;比如懒汉式单例的获取方式中在并发量比较大的时候就有可能会出现问题?如一个线程A执行到lazySingleton = new LazySingleton();但是还没有获取到对象(对象的获取是需要时间的),第二个线程B也在执行,执行到if (lazySingleton == null) 时,判断为真,这样内存中就会有两个对象了。
解决线程不安全的方法有很多,可以在newInstance方法前加synchronized关键字,也可以在newInstance方法内增加synchronized 同步代码块的形式实现,但都不是最优秀的单例模式(建议读者使用饿汉式)
其次,需要考虑对象的复制情况。在java中,对象默认是不可以被复制的,若实现了Cloneable接口,并实现了clone方法,则可以直接通过复制方式创建一个新对象,对象的复制时不需要调用类的构造器的,因此即使构造被私有化,对象仍然可以被复制,解决该方法最好的方法就是不去实现Cloneable接口
最佳实践
如在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命周期,决定什么时候创建出来,什么时候销毁,以及销毁时做怎样的处理等,如果采用非单例模式(Prototype类型),则Bean的初始化后的管理交由J2EE容器,Spring容器不会再跟踪管理Bean的生命周期。
使用单例模式需要注意的一点的就是JVM的垃圾回收机制,如果我们的一个单例对象在内存中长久不实用,jvm就会认为该对象是一个垃圾,在cpu资源空闲的情况下就会将该对象清理掉,下次调用时就需要重新产生一个对象。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现恢复原状的情况,应用就会出现问题。如果确实需要单例模式来记录有状态的值,哟两种方法可以解决
- 1、由容器管理单例的生命周期
Java EE 容器或者Spring可以让对象长久的驻留在内存中。当然也可以通过管理对象的生命周期,但既然有工具提供给我们,为什么不用呢? - 2、状态随时记录
可以使用异步记录的方式,或者使用观察者模式,记录状态的变化,写入文件或者写入数据库中确保即使单例对象重新初始化也可以从资源环境中获取销毁前的数据,避免应用数据的丢失。
网友评论