写在最前面
单例模式是一个非常容易理解,也非常容易实现和使用的设计模式,但是我们需要足够小心的去使用它,绝大部分情况下,使用单例模式都不是一个好的选择。推荐仅用于了解设计模式的思想。
什么是单例模式?
单例对象的类必须保证只有一个实例存在
构造对象的方法
- 构造器 new Instance()
- 序列化反序列化获取
防止多次构造对象
- 防止通过构造器获取对象
- 将构造器设为private 防止直接调用
- 手动构造一个实例固定返回
-
问题:多线程安全和通过反射构造实例
- 防止通过序列化反序列化多次获取实例
常见单例实现方式优缺点
-
饿汉模式
- 在静态代码块中构造唯一的实例 示例代码如下
public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton newInstance(){ return instance; } }
优点: 线程安全(why?见文末) 实现较为简单public class Singleton{ private static Singleton instance; static{ instance=new Singleton(); } private Singleton(){} public static Singleton newInstance(){ return instance; } }
缺点: 相较于懒汉模式,加载压力大。没有解决反射和序列化的问题
- 在静态代码块中构造唯一的实例 示例代码如下
-
懒汉模式
-
在获取时判断,为空则构造实例
//V1.0 public class Singleton{ private static Singleton instance = null; private Singleton(){} public static Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
如上代码多线程下可能创建多个实例,即轮流判断是否为null都进入if语句进行初始化 最简单的思路 上锁!
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static synchronized Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
太慢了,如果多个线程同时获取,效率很低。著名的double-check模式
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
看起来超完美了,懒加载线程安全效率也高,然而,由于jvm的指令重排序功能,导致这段代码仍然存在问题。对象的创建分为三个步骤
- 分配空间并赋默认值
- 对象初始化 <init>函数 即构造器
- 地址分配 即建立引用关系
除了第一步是固定在最早发生的,后面两步的顺序不是固定的,而判断对象是不是null只需要第三步完成就不是null了,可能这时候还没有完成对象的初始化过程,使用的过程中就会出错!
想要取消指令重排序就要用到jdk1.5以后推出的volatile关键字,这个关键字会形成内存栅栏防止前后重排序。所以,最终版代码如下public class Singleton { private static volatile Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
-
-
静态内部类
通过类加载机制同时达到线程安全和完成懒加载(内部类只有在被调用时才加载)public class Singleton{ private static class SingletonHolder{ public static Singleton instance = new Singleton(); } private Singleton(){} public static Singleton newInstance(){ return SingletonHolder.instance; } }
-
枚举 (推荐方式但应用较少)
public enum Singleton{ INSTANCE; }
具有以下优点
- 线程安全 枚举实际上元素都是static final类型的在类加载时完成
-
能够抵抗反射的攻击
枚举类构造函数 - 能够抵抗序列化攻击
枚举类的序列化函数是使用valueOf()取出的枚举类型的名字,而反序列化也是根据名字来找到对应的实例,所以序列化也不会产生额外的对象
缺点:
- 不是懒加载
为什么类加载是线程安全的?
虚拟机类加载器在类初始化时会给<clinit>()方法上锁保证线程安全,所以如果<clinit>()方法中有耗时操作,可能会产生难以发现的阻塞。
网友评论