java中全面的单例模式多种实现方式总结

单例模式的思想

想整理一些 java 并发相关的知识,不知道从哪开始,想起了单例模式中要考虑的线程安全,就从单例模式开始吧。
之前写过单例模式,这里再从新汇总补充整理一下,单例模式的多种实现。html

单例模式那件小事,看了你不会后悔
单例模式不是一件小事,快回来看看java

以前在第一篇文章说,单例模式的主要思想是:安全

  • 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象;
  • 声明一个私有的静态的实例对象,供外界使用;
  • 提供一个公开的方法,让外界得到该类的实例对象

这种说法看上去没错,但也好像不太准确。其实,就算外界能随意 new 出新的实例对象,但只要咱们保证咱们每次使用的对象是惟一的,就能够。并发

单例模式的 N 种实现方式

饿汉式(线程安全,可用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton = new Singleton();

    public static Singleton getInstance() {
        return sSingleton;
    }
}
  • 缺点: 类一加载的时候,就实例化,提早占用了系统资源。

常量式(线程安全,可用)

public class Singleton {
    private Singleton() {
    }

    public static final Singleton sSingleton = new Singleton();
}

将实例对象用 public static final 修饰,不提供公开方法获取实例,直接经过 Singleton.sSingleton 获取。函数

  • 缺点:与饿汉式同样,类一加载的时候,就实例化,提早占用了系统资源。

懒汉式(线程不安全,并发场景不可用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton;

    public static Singleton getInstance() {
        if (sSingleton == null) {
            sSingleton = new Singleton();
        }
        return sSingleton;
    }
}
  • 缺点:第一次第一次加载时反应稍慢,线程不安全。

同步的懒汉式?(线程安全,可用,不建议使用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton;

    public synchronized static Singleton getInstance() {
        if (sSingleton == null) {
            sSingleton = new Singleton();
        }
        return sSingleton;
    }
}
  • 缺点:第一次加载时反应稍慢,每次调用 getInstance 都进行同步,形成没必要要的同步开销,这种模式通常不建议使用。

双重检查锁 DCL (线程安全,大多数场景知足需求,推荐使用)

public class Singleton {
    private Singleton() {
    }

    /**
     * volatile is since JDK5
     */
    private static volatile Singleton sSingleton;

    public static Singleton getInstance() {
        if (sSingleton == null) {
            synchronized (Singleton.class) {
                // 未初始化,则初始instance变量
                if (sSingleton == null) {
                    sSingleton = new Singleton();
                }
            }
        }
        return sSingleton;
    }
}

sSingleton = new Singleton() 不是一个原子操做。(XXX)故须加 volatile 关键字修饰,该关键字在 jdk1.5 以后版本才有。高并发

  • 优势:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。
  • 缺点:第一次加载时反应稍慢,也因为Java内存模型的缘由偶尔会失败。在高并发环境下也有必定的缺陷,虽然发生的几率很小。DCL模式是使用最多的单例实现方式,它可以在须要时才实例化单例对象,而且可以在绝大多数场景下保证单例对象的惟一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,不然这种方式通常可以知足需求。

静态内部类(线程安全,推荐使用)

public class Singleton {

    private Singleton () {
    }

    private static class InnerClassSingleton {
     private final static Singleton sSingleton = new Singleton();
    }

    public static Singleton getInstance() {
        return InnerClassSingleton.sSingleton;
    }
}

优势:推荐使用。测试

枚举单例(线程安全,不建议使用)

public enum Singleton{
    INSTANCE;
    
    // 其它方法
    public void doSomething(){
        ...
    }
}
  • 优势:枚举实现单例很简单,也很安全。
  • 缺点:经验丰富的 Android 开发人员都会尽可能避免使用枚举。官方文档有说明:相比于静态常量Enum会花费两倍以上的内存。

另类实现——利用容器实现单例

import java.util.HashMap;
import java.util.Map;

public class Singleton {
    private static Map<String, Object> objMap = new HashMap<String, Object>();

    private Singleton() {
    }

    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }
}

利用了 HashMap 容器 key 不可重复的特性。线程

  • 优势:这种实现方式使得咱们能够管理多种类型的单例,而且在使用时能够经过统一接口进行获取操做,下降用户使用成本,也对用户隐藏了具体实现,下降耦合度。
  • 缺点:没有私有化构造方法,用户能够 new 出新的实例对象。

防止反射破坏单例

前面的多种实现方法中,不少咱们按照构造方法私有化的思想来实现的,咱们知道,利用反射,仍然能够建立出新对象,这样在反射场景中,这种思想实现的单例模式就失效了,那么如何防止反射破坏单例模式呢?原理上就是在存在一个实例的状况下,再次调用构造方法时,抛出异常。下面以静态内部类的单例模式为例:code

public class Singleton {
    private static boolean flag = false;  
  
    private Singleton(){  
        synchronized(Singleton.class)  
        {  
            if(flag == false)  
            {  
                flag = !flag;  
            }  
            else  
            {  
                throw new RuntimeException("单例模式被侵犯!");  
            }  
        }  
    }  

    private static class InnerClassSingleton {
     private final static Singleton sSingleton = new Singleton();
    }

    public static Singleton getInstance() {
        return InnerClassSingleton.sSingleton;
    }
}

具体测试代码,见 单例模式不是一件小事,快回来看看htm

防止序列化和反序列化破坏单例

经过序列化能够讲一个对象实例写入到磁盘中,经过反序列化再读取回来的时候,即使构造方法是私有的,也依然能够经过特殊的途径,建立出一个新的实例,至关于调用了该类的构造函数。要避免这个问题,咱们须要在代码中加入以下方法,让其在反序列化过程当中执行 readResolve 方法时返回 sSingleton 对象。

private Object readResolve() throws ObjectStreamException {
    return sSingleton;
}

结语

有没有一种方式实现的单例模式在任何状况下都是一个单例呢?

—— 有。就是上面说的枚举单例。枚举,就能保证在任何状况下都是单例的,而且是线程安全的。

相关文章
相关标签/搜索