Java 中单例 (Singleton) 模式是一种广泛使用的设计模式单例模式的主要作用是保证在 Java 程序中, 某个类只有一个实例存在
使用单例模式能够避免实例对象的重复创建, 减少创建对象的时间开销并节约内存空间
1. 饿汉模式
饿汉的意思是在类加载的时候就创建对象实例, 不管实际是否需要创建这样避免了多线程下多个线程创建了多个实例的问题但缺点也很明显, 类加载之后就被创建看, 浪费了内存空间
- public class StarveManSingleton {
- private static StarveManSingleton starveManSingleton = new StarveManSingleton();
- private StarveManSingleton(){}
- public static StarveManSingleton getSingleton(){
- return starveManSingleton;
- }
- }
2. 懒汉模式
如果单例占用的内存比较大, 或单例只是在某个特定场景下才会用到, 最好进行延迟加载
懒汉模式中, 单例在需要的时候才去创建的, 如果单例已经创建, 再次调用获取接口将不会重新创建新的对象, 而是直接返回之前创建的对象
线程不安全实现
- public class LazyManSingleton {
- private static LazyManSingleton lazyManSingleton;
- private LazyManSingleton(){}
- public static LazyManSingleton getSingleton(){
- if (lazyManSingleton == null) {
- lazyManSingleton = new LazyManSingleton();
- }
- return lazyManSingleton;
- }
- }
上面的懒汉模式并没有考虑线程安全问题, 在多个线程可能会并发调用它的 getInstance()方法, 导致创建多个实例
线程安全实现
- public class StarveManSingleton1 {
- private static StarveManSingleton1 starveManSingleton1;
- private StarveManSingleton1(){}
- public static synchronized StarveManSingleton1 getSingleton(){
- if (starveManSingleton1 == null) {
- starveManSingleton1 = new StarveManSingleton1();
- }
- return starveManSingleton1;
- }
- }
但是这种实现存在性能问题, synchronized 修饰的同步方法比一般方法要慢很多, 如果多次调用 getInstance(), 累积的性能损耗比较大
3. 兼顾线程安全和效率的写法 - 双重锁检查
- public class DoubleLockSingleton {
- private static DoubleLockSingleton doubleLockSingleton;
- private DoubleLockSingleton() {
- }
- public static DoubleLockSingleton getSingleton() {
- if (doubleLockSingleton == null) {
- synchronized (DoubleLockSingleton.class) {
- if (doubleLockSingleton == null) {
- doubleLockSingleton = new DoubleLockSingleton();
- }
- }
- }
- return doubleLockSingleton;
- }
- }
双重检查锁, 顾名思义, 在加锁前多进行一次 null 检查可以减少绝大多数的加锁操作, 提高执行效率
双重检查锁是否真的就万无一失了呢?
由于指令重排优化的存在, 导致初始化 Singleton 和将对象地址赋给 instance 字段的顺序是不确定的在某个线程创建单例对象时, 在构造方法被调用之前, 就为该对象分配了内存空间并将对象的字段设置为默认值此时就可以将分配的内存地址赋值给 instance 字段了, 然而该对象可能还没有初始化若紧接着另外一个线程来调用 getInstance, 取到的就是状态不正确的对象, 程序就会出错
简而言之就是线程 A 在锁之前, 线程 B 取到了 singleton 的 null 引用, 锁之后 A 创建的单利没有同步更新给线程 B
为此需要加个 volatile 描述符实现线程间数据一致性
- public class DoubleLockSingleton {
- private volatile static DoubleLockSingleton doubleLockSingleton;
- private DoubleLockSingleton() {
- }
- public static DoubleLockSingleton getSingleton() {
- if (doubleLockSingleton == null) {
- synchronized (DoubleLockSingleton.class) {
- if (doubleLockSingleton == null) {
- doubleLockSingleton = new DoubleLockSingleton();
- }
- }
- }
- return doubleLockSingleton;
- }
- }
另外双重检查锁只在 JDK 1.5 之后才有效
4. 静态内部类
- public class StaticInnerClassSingleton {
- private static class SingletonHolder{
- private static final StaticInnerClassSingleton SINGLETON = new StaticInnerClassSingleton();
- }
- private StaticInnerClassSingleton(){}
- public static StaticInnerClassSingleton getSingletion(){
- return SingletonHolder.SINGLETON;
- }
- }
这种方式下 Singleton 类被装载了, instance 不一定被初始化因为 SingletonHolder 类没有被主动使用, 只有显示通过调用 getInstance 方法时, 才会显示装载 SingletonHolder 类, 从而实例化 instance
5. 枚举写法
上面的四种单例写法都需要额外的工作来实现序列化, 否则每次反序列化一个序列化的对象时都会创建一个新的实例且无法阻止通过反射强行调用私有构造函数
枚举类很好的解决了这两个问题, 使用枚举除了线程安全和防止反射调用构造器之外, 还提供了自动序列化机制, 防止反序列化的时候创建新的对象
- public enum EnumSingleton {
- SINGLETON;
- }
单元素的枚举类型已经成为实现 Singleton 的最佳方法
来源: http://www.jianshu.com/p/d4d3ef0d5863