目录:
1, 单例模式 (Singleton Pattern)
2, 概念
3, 饿汉式: 不是延迟加载, 加载类的时候直接初始化
4, 懒汉式: 延迟加载, 首次需要使用的时候在实例化, 需要考虑线程安全
5, 静态内部类实现
6, 枚举实现
1, 单例模式 (Singleton Pattern) <== 返回目录
确保一个类只有一个实例, 并提供一个全局访问点.
2, 概念 <== 返回目录
下面的内容转载自博客: 23 种设计模式之单例模式 ( https://chenmingyu.top/design-singleton/) https://chenmingyu.top/design-singleton/
单例模式: 单例模式属于创建型模式;
定义: 确保某一个类只有一个实例, 而且自行实例化并向整个系统提供这个实例. 单例模式目的是保证在程序运行期间一个类只有一个实例, 并提供一个全局访问点, 无论什么情况下, 只会生成一个实例, 免去繁琐的创建销毁对象的过程.
优点:
) 减少了内存开支, 避免频繁地创建, 销毁对象;
) 避免对资源的多重占用;
缺点:
没有接口, 不能继承, 与单一职责原则冲突, 一个类应该只关心内部逻辑, 而不关心外面怎么样来实例化
如何设计单例模式
如何设计单例模式其实很简单, 只需要考虑一个问题, 实例是否可以保证是全局唯一. 关于实例是否保证是全局唯一延伸出的问题:
是否线程安全, 不安全肯定就不能保证全局只有一个实例
是否支持序列化, 支持序列化的类, 被反序列化之后肯定就不是全局唯一了
是否支持反射, 支持反射肯定也不是全局唯一的
是否可以被克隆, 这个也不能保证全局唯一
所以设计一个安全的单例需要考虑的问题还是很多的. 针对上述问题常见的解决办法:
保证线程安全, 使用 volatile+synchronized 实现
防止序列化攻击, 重写 readResolve 方法
防止反射, 常用的方案是在单例类里增加一个 boolean 类型的 flag 标识, 在实例化的时候先判断 flag 标识
防止克隆, 重写 clone() 方法
实现一个最简单的单例就需要考虑到以上的所有问题, 这个时候什么有用的方法还没写那, 代码就已经很多了, 那有没有简单的办法既满足上述条件, 代码又简洁那, 那肯定有, 使用枚举实现单例.
常见的单例模式实现
常见的单例模式实现方案大概有五种, 懒汉模式, 饿汉模式, 双重检查方式实现, 静态内部类实现, 枚举实现
分个类:
是否支持延迟加载, 分为懒汉模式和饿汉模式
线程安全设计了双重检查模式实现, 静态内部类实现方式
不支持序列化, 反射, 克隆, 枚举实现方式
其中懒汉模式, 饿汉模式, 双重检查方式实现, 静态内部类的实现方式都可以概括为以下两步:
1. 构造函数私有化, 保证在外部无法 new 对象
2. 提供一个 static 方法获取当前实例 (不同方案, 实现不同)
当然枚举的实现方式最简单, 也最安全的, 所以推荐使用枚举实现, 其次推荐使用静态内部类方式实现.
3, 饿汉式: 不是延迟加载, 加载类的时候直接初始化 <== 返回目录
优点: 线程安全, 代码简单.
缺点: 不是延迟加载, 如果你用不到这个类, 它也会实例化, 还有一个问题就是如果这个实例依赖外部一些配置文件, 参数什么的, 在实例化之前就要获取到, 否则就实例化异常
- /**
- * 单例模式: 饿汉式
- * @author oy
- * @date 2019 年 9 月 3 日 下午 11:13:49
- */
- public class Singleton {
- private static Singleton singleton = new Singleton();
- private Singleton() {
- }
- public static Singleton getInstance(){
- return singleton;
- }
- }
4, 懒汉式: 延迟加载, 首次需要使用的时候在实例化, 需要考虑线程安全 <== 返回目录
线程不安全的实现方式:
- /**
- * 单例模式: 懒汉式, 线程不安全
- * @author oy
- * @date 2019 年 9 月 3 日 下午 11:15:47*/
- public class Singleton {
- private static Singleton singleton;
- private Singleton() {}
- public static Singleton getInstance(){
- if(null == singleton){
- singleton = new Singleton();
- }
- return singleton;
- }
- }
线程安全的实现方式: 双重检查 (DCL:Double Check Lock)
- /**
- * 单例模式: 双重检查 (DCL:Double Check Lock)
- * @author oy
- * @date 2019 年 9 月 3 日 下午 11:17:06
- */
- public class Singleton {
- private static volatile Singleton singleton;
- private Singleton() {}
- public static Singleton getInstance(){
- if(null == singleton){
- synchronized (Singleton.class){
- if(null == singleton){
- singleton = new Singleton();
- }
- }
- }
- return singleton;
- }
- }
面试题: 为什么使用 volatile 修饰 singleton 变量?
1. 说的 volatile, 首先肯定回答 volatile 的可见性
2. 防止重排序优化, 如果不用 volatile 修饰, 多线程的情况下, 可能会出现线程 A 进入 synchronized 代码块,
执行 new Singleton();, 首先给 singleton 分配内存, 但是还没有初始化变量,
这时候线程 B 进入 getInstance 方法, 进行第一个判断, 此时 singleton 已经不为空,
直接返回 singleton, 然后肯定报错. 使用 volatile 修饰之后禁止 jvm 重排序优化, 所以就不会出现上面的问题
5, 静态内部类实现 <== 返回目录
使用静态内部类实现也是延迟加载, 利用静态内部类去实现线程安全, 只有在第一次调用 getInstance 方法的时候才会去加载 SingletonHolder, 初始化 SINGLETON
- /**
- * 单例模式: 静态内部类实现
- * @author oy
- * @date 2019 年 9 月 3 日 下午 11:17:06
- */
- public class Singleton {
- private Singleton() {}
- public static Singleton getInstance() {
- return SingletonHolder.SINGLETON;
- }
- private static class SingletonHolder {
- private static final Singleton SINGLETON = new Singleton();
- }
- }
6, 枚举实现 <== 返回目录
枚举实现代码更简洁, 线程安全, 并且保证枚举不会被反序列化, 反射和克隆
- /**
- * 单例模式: 枚举实现
- * @author oy
- * @date 2019 年 9 月 3 日 下午 11:17:06
- */
- public enum Singleton {
- SINGLETON;
- /**
- * 提供的方法
- */
- public void method() {
- System.out.println("枚举实现");
- }
- }
来源: http://www.bubuko.com/infodetail-3182416.html