1.饿汉式


饿汉很饿,只想着尽早吃到。所以他就在最早的时机,即类加载时初始化单例,以后访问时直接返回即可。

  1. // 饿汉
  2. // ThreadSafe
  3. public class Singleton {
  4. private static final Singleton singleton = new Singleton();
  5. private Singleton() {
  6. }
  7. public static Singleton getInstance() {
  8. return singleton;
  9. }
  10. }

饿汉的好处是天生的线程安全(得益于类加载机制),写起来超级简单,使用时没有延迟;坏处是有可能造成资源浪费(如果类加载后就一直不使用单例的话)。

值得注意的时,单线程环境下,饿汉与饱汉在性能上没什么差别;但多线程环境下,由于饱汉需要加锁,饿汉的性能反而更优。

2.饱汉式


2.1基础饱汉

饱汉,即已经吃饱,不着急再吃,饿的时候再吃。所以他就先不初始化单例,等第一次使用的时候再初始化,即“懒加载”。

public class Singleton {
  private static Singleton singleton = null;
  private Singleton1() {
  }
  public static Singleton getInstance() {
    if (singleton == null) {
      singleton = new Singleton();
    }
    return singleton;
  }
}

2.2 DCL

public class Singleton{
  private static volatile Singleton singleton = null; //volatile修饰

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (singleton == null) {
      synchronized (Singleton.class) {
        if (singleton == null) {
        //当Thread1和Thread2,两个线程同时判断instance == null成立,此时,它们同时试图去获得锁
        //由于synchronized的排它性,假设此时只有Thread1获得锁,Thread2被阻塞
        //当Thread1执行完 new Singleton()操作之后,释放当前锁
        //接下来,Thread2获得锁,此时Thread2会继续执行new Singleton()操作
        //此时,Thread1和Thread2,就获得了不一样的instance实例
          singleton = new Singleton();
        }
      }
    }
    return singleton;
  }
}

volatile 修饰目的: 对象实际上创建对象要进过如下几个步骤:

  • 分配内存空间。
  • 调用构造器,初始化实例。
  • 返回地址给引用

可能发生指令重排序的,那有可能构造函数在对象初始化完成前就赋值完成了,在内存里面开辟了一片存储区域后直接返回内存的引用,这个时候还没真正的初始化完对象。、、、 但是别的线程去判断instance!=null,直接拿去用了,其实这个对象是个半成品,那就有空指针异常了。

3.Holder


既希望利用饿汉模式中静态变量的方便和线程安全;又希望通过懒加载规避资源浪费。Holder模式满足了这两点要求:核心仍然是静态变量,足够方便和线程安全;通过静态的Holder类持有真正实例,间接实现了懒加载。

public class Singleton {
  private static class SingletonHolder {
    private static final Singleton singleton = new Singleton();
    private SingletonHolder() {
    }
  }

  private Singleton() {
  }


  public static Singleton getInstance() {
    return SingletonHolder.singleton;
  }
}