一、单例模式思想
单例模式的主要思想是:
- 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象;
- 声明一个私有的静态的实例对象,供外界使用;
- 提供一个公开的方法,让外界获得该类的实例对象。
这种说法看上去没错,但也好像不太准确。其实,就算外界能随意 new 出新的实例对象,但只要我们保证我们每次使用的对象是唯一的,就可以。
二、单例模式实现方式
1. 饿汉式(线程安全,可用)
public class Singleton {
private Singleton() {
}
private static Singleton sSingleton = new Singleton();
public static Singleton getInstance() {
return sSingleton;
}
}
- 优点:JVM在加载这个类时马上创建此类唯一的单例实例,在任何线程访问sSingleton 静态变量之前,保证了线程安全;
- 缺点: 类一加载的时候,就实例化,提前占用了系统资源。如果程序在运行过程中没有用到该单例静态变量,势必会造成资源浪费。
2. 懒汉式(线程不安全,并发场景不可用)
public class Singleton {
private Singleton() {
}
private static Singleton sSingleton;
public static Singleton getInstance() {
if (sSingleton == null) {
sSingleton = new Singleton();
}
return sSingleton;
}
}
- 优点:延迟实例化,即用到时才会实例化;
- 缺点:第一次加载时反应稍慢,线程不安全,无法用于并发场景。
3. 同步的懒汉式(线程安全,可用,不建议使用)
public class Singleton {
private Singleton() {
}
private static Singleton sSingleton;
public synchronized static Singleton getInstance() {
if (sSingleton == null) {
sSingleton = new Singleton();
}
return sSingleton;
}
}
缺点:性能比较低,每次调用 getInstance( ) 都需要进行同步(去抢锁),实际上我们仅需要第一次执行该方法时进行同步,造成了不必要的同步开销,同步一个方法可能会造成程序执行效率下降100倍,这种模式一般不建议使用。
4. 双重检查锁 (线程安全,大多数场景满足需求,推荐使用)
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;
}
}
- 优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,并且线程安全;
- 缺点:由于Java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷,虽然发生的概率很小。
双重检查锁进行了两次判断,第一次是为了判断是否已经被实例化,第二次是为了进行同步,避免多线程问题。由于 sSingleton = new Singleton()
不是一个原子操作,对象的创建在JVM中可能会进行指令重排,在多线程访问下存在风险,使用volatile修饰解决该问题。双重检查锁是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。
5. 静态内部类(线程安全,推荐使用)
public class Singleton {
private Singleton () {
}
private static class InnerClassSingleton {
private final static Singleton sSingleton = new Singleton();
}
public static Singleton getInstance() {
return InnerClassSingleton.sSingleton;
}
}
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁(因为JVM在初始化内部类时,会保证只有一个线程去初始化这个内部类),其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
6. 枚举单例(线程安全)
public enum Singleton{
INSTANCE;
// 其它方法
public void doSomething(){
...
}
}
- 优点:枚举实现单例很简单,也很安全,并且可以防止反射破坏单例;
- 缺点:可读性较差,且相比于静态常量Enum会花费两倍以上的内存。
枚举单例模式是在《Effective Java》中推荐的单例模式之一,但枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。在以上所有的单例模式中,推荐静态内部类单例模式。主要是非常直观,即保证线程安全又保证唯一性。