单例模式(Singleton Pattern)
定义:
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。)
类图
代码
/**
* 饿汉模式:
* 线程安全,但是对象不是懒加载
*/
public class Singleton{
private static final Singleton instance = new Singleton();
// 私有化构造方法
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
/**
* 懒汉模式:
* 获取实例的方法添加的synchronized关键字,进行操作加锁,这就导致性能下降。
*/
public class Singleton{
private static final Singleton instance;
// 私有化构造方法
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
/**
* 懒汉模式:Double check(双重检查)
*
*/
public class Singleton{
private static final Singleton instance;
// 私有化构造方法
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
/**
* 利用静态内部类的方式实现单例类,利用了Java 静态内部类的特性:
* Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例。
*/
public class Singleton{
// 私有化构造方法
private Singleton(){}
private static class SingletonInner {
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonInner.instance;
}
}
/**
* 利用枚举实现
*/
public class Singleton{
// 私有化构造方法
private Singleton(){}
private static enum SingletonEnum {
INSTANCE;
private Singleton instance = null;
private SingletonEnum(){
instance = new Singleton();
}
}
public static Singleton getInstance(){
return SingletonEnum.INSTANCE.instance;
}
}
为什么要用单例模式?
先说说单例模式的优点:
- 由于单例模式在内存中只有一个实例,减少了内存开支;
- 只生成一个实例,减少了性能开销;
- 可以避免对资源的多重占用;
- 单例模式可以在系统设置全局的访问点,优化和共享资源访问。
说完优点,我们也说说缺点:
- 单例模式一般没有接口,所以扩展困难;
- 单例模式与单子职责原则有冲突。一个类应该只实现一个逻辑,而不关心是否是单例的,是不是要单例取决于环境。
使用场景
- 要求生成唯一序号的环境;
- 在整个项目中需要一个共享访问点或共享数据,例如:统计在线人数等;
- 创建一个对象需要消耗的资源过多的场景,如访问io和数据库等资源;
- 需要定义大量静态常量和静态方法(如工具类)的环境,可以采用单例模式(也可以直接声明为static)。
结束语
单例模式是23个设计模式中比较简单的,应用也比较广泛,比如Spring中,每个Bean默认就是单例,这样做的优点是Spring容器可以管理这些Bean的生命周期(创建、初始化、使用、销毁)。
使用单例模式的时候,我们要注意JVM的垃圾回收机制,如果我们创建的单例对象长时间不使用,被JVM当做垃圾清理掉,下次调用就需要重新创建一个对象,此时这个类的之前记录的状态值就会出现恢复原状的情况。
针对这种情况解决方式:
- 我们可以由容器管理单例对象的生命周期;
- 状态随时记录,采用异步记录或者使用观察者模式,记录状态的变化,写入文件或写入数据库中,避免应用数据丢失。