Singleton(单例)模式
-
概念
- 顾名思义就是该类只有一个实例,并且确保在任何情况下都绝对只有一个实例,并且程序上也要表现出“只存在一个实例”
-
优点
- 只有一个对象,节省内存
- 可以实现避免对资源的多重占用
- 在系统设置全局访问点,优化和共享资源访问
-
实现思路
- 静态化实例对象
- 私有构造方法,禁止使用构造器方法来创建实例
- 提供一个公共静态方法,用来返回唯一实例
-
适用场景
- 需要频繁实例化后销毁的对象
- 创建对象时耗时过多或者耗资源过多,但又经常使用到的对象
- 有状态的工具类对象
- 频繁访问数据库或文件的对象
- 资源共享的情况下,避免优化资源操作时导致的性能损耗等,如日志文件、应用配置
- 控制资源的情况下,方便资源之间的互相通信,如线程池
-
懒汉式
- 懒汉式的意思就是只有当时候的时候采取创建实例,比较“懒”
- 这种方式当多线程使用时,就存在线程安全问题,可能同时调用getInstance(),创建出多个实例
public class Singleton{ private static Singleton singleton; //设置构造函数为私有,避免外部通过new来创建对象,保证只有一个接口去创建对象 private Singleton(){} //并且只创建一个对象 public static Singleton getInstance(){ if(singleton == null){ instance = new Singleton(); } return singleton; } }
为了考虑线程安全问题,因此使用Synchronized关键字来解决
public class Singleton{ private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleton == null){ instance = new Singleton(); } return singleton; } }
这种方式虽然确保了线程安全,但是可能会引起大量的阻塞,影响性能
-
饿汉式
- 饿汉式的意思就是在初始化时已经生成好实例,使用的时候直接返回即可了,比较“勤“,所以容易”饿“
- 这种方式存在一个问题就是不管有没有用到,都先生成实例,虽然没有线程安全问题,但是会浪费内存空间
public class Singleton{ private static Singleton singleton = new Singleton; private Singleton(){} public static Singleton getInstance(){ return singleton; } }
-
双重校验
- 双重校验也是一种延迟加载,但是通过双重校验解决了线程安全的时候效率低下的问题
- 这种方法和懒汉式加Synchronized关键字的方法对比,仅仅对一部分代码加了锁,并不是所有调用这个方法的对象都会发生阻塞,只有第一次判断为null时,才进入同步代码块,并且再里面会再一次进行判断,如果其他线程已经创建了,那么就可以直接返回了
public class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ synchronized(Singleton.class){ if(singleton == null){ instance = new Singleton(); } } } return singleton; } }
-
静态内部类方式
- Java静态内部类的特性是,加载的时候不会加载静态内部类,到使用的时候才会加载
- 采用静态内部类的方式,只有使用的时候才加载,并且此时的类加载又是线程安全的
public class Singleton{ private static class SingletonInner{ private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return SingletonInner.singleton; } }
-
枚举方式
- 枚举提供了一种方式可以取代大量的static final类型的变量。
- 枚举方式实现了Serializable接口,所以不用考虑序列化的问题
- JVM能确保只加载一个实例,来保证线程安全。
public class Singleton{ private Singleton(){} private enum SingletonEnum{ SINGLETON; private final Singleton singleton; SingletonEnum(){ singleton = new Singleton(); } private Singleton getInstance(){ return singleton; } } public static Singleton getInstance(){ return SingletonEnum.SINGLETON.getInstance(); } }
-
破坏单例模式的方法及解决办法
- 除了枚举方式外,其他方法都可以通过反射来破坏单例模式,因此可以在构造方法中进行判断,如果已经有实例,则阻止生成新的实例。
- 如果单例实现了序列化接口Serializable,就可以通过反序列化来破坏单例,所以可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(),反序列化时直接返回相关对象。
-
参考文献
https://zhuanlan.zhihu.com/p/80127173
https://blog.csdn.net/absolute_chen/article/details/93380566
《图解设计模式》