1.饿汉式

  1. // final 不允许被继承
  2. public class Singleton {
  3. // 实例变量
  4. private byte[] data = new byte[1024];
  5. // 在定义实例对象的时候直接初始化
  6. private static Singleton instance = new Singleton();
  7. // 私有构造函数,不允许外部new
  8. private Singleton() {
  9. }
  10. public static Singleton getInstance() {
  11. return instance;
  12. }
  13. }

instance作为类变量在类初始化的过程中会被收集进() 方法中, 该方法能够百分之百地保证同步, 也就是说instance在多线程的情况下不可能被实例化两次, 但是instance被ClassLoader加载后可能很长一段时间才被使用, 那就意味着instance实例所开辟的堆内存会驻留更久的时间。

2.懒汉式

所谓懒汉式就是在使用类实例的时候再去创建(用时创建),这样就可以避免类在初始化时提前创建,懒汉式示例代码如所示。

  1. // final 不允许被继承
  2. public class Singleton1 {
  3. // 实例变量
  4. private byte[] data = new byte[1024];
  5. // 在定义实例对象的时候直接初始化
  6. private static Singleton1 instance = null;
  7. // 私有构造函数,不允许外部new
  8. private Singleton1() {
  9. }
  10. public static Singleton1 getInstance() {
  11. if ( null == instance )
  12. instance = new Singleton1();
  13. return instance;
  14. }
  15. }

Singleton的类变量instance=null, 因此当Singleton.class被初始化的时候instance并不会被实例化, 在getInstance方法中会判断instance实例是否被实例化, 看起来没有什么问题, 但是将getInstance方法放在多线程环境下进行分析, 则会导致instance被实例化一次以上,并不能保证单例的唯一性,如图所示。

image.png

3.懒汉式+同步方法

  1. // final 不允许被继承
  2. public class Singleton2 {
  3. // 实例变量
  4. private byte[] data = new byte[1024];
  5. // 在定义实例对象的时候直接初始化
  6. private static Singleton2 instance = null;
  7. // 私有构造函数,不允许外部new
  8. private Singleton2() {
  9. }
  10. // 向getInstance方法加入同步控制,每次只能有一个线程能够进入
  11. public static synchronized Singleton2 getInstance() {
  12. if ( null == instance )
  13. instance = new Singleton2();
  14. return instance;
  15. }
  16. }

采用懒汉式+数据同步的方式既满足了懒加载又能够百分之百地保证instance实例的唯一性, 但是synchronized关键字天生的排他性导致了 getInstance方法只能在同一时刻被一个线程所访问,性能低下。

4.Double-Check

Double-Check是一种比较聪明的设计方式, 他提供了一种高效的数据同步策略, 那就是首次初始化时加锁, 之后则允许多个线程同时进行getInstance方法的调用来获得类的实例, Double-Check的示例代码如所示。

  1. import java.net.Socket;
  2. import java.sql.Connection;
  3. // final 不允许被继承
  4. public class Singleton3 {
  5. // 实例变量
  6. private byte[] data = new byte[1024];
  7. // 在定义实例对象的时候直接初始化
  8. private static Singleton3 instance = null;
  9. Connection conn;
  10. Socket socket;
  11. // 私有构造函数,不允许外部new
  12. private Singleton3() {
  13. this.conn=null; //
  14. this.socket=null;// 初始化操作
  15. }
  16. public static Singleton3 getInstance() {
  17. // 当instance为null时,进入同步代码块,同时判断避免了每次都需要进入同步代码块,可以提高效率
  18. if ( null == instance ) {
  19. // 只有一个线程能够获得Singleton.class关联的monitor
  20. synchronized (Singleton3.class) {
  21. // 判断如果instance为null则创建
  22. if ( null == instance ) {
  23. instance = new Singleton3();
  24. }
  25. }
  26. }
  27. return instance;
  28. }
  29. }

当两个线程发现null==instance成立时, 只有一个线程有资格进入同步代码块, 完成对instance的实例化, 随后的线程发现null==instance不成立则无须进行任何动作, 以后对getInstance的访问就不需要数据同步的保护了。

在Singleton的构造函数中,需要分别实例化conn和socket两个资源,还有Singleton自身,根据JVM运行时指令重排序和Happens-Before规则,这三者之间的实例化顺序并无前后关系的约束, 那么极有可能是instance最先被实例化, 而conn和socket并未完成实例化,未完成初始化的实例调用其方法将会抛出空指针异常,下面我们结合图来进行分析。
image.png

5.Volatile+Double-Check

Double-Check虽然是一种巧妙的程序设计, 但是有可能会引起类成员变量的实例化conn和socket发生在instance实例化之后, 这一切均是由于JVM在运行时指令重排序所导致的, 而volatile关键字则可以防止这种重排序的发生, 因此代码稍作修改即可满足多线程下的单例、懒加载以及获取实例的高效性,代码修改如下:

  1. private volatile static singleton instance =null;

6.Holder方式

Holder的方式完全是借助了类加载的特点, 下面我们对整个单例模式进行重构, 然后结合类加载器的知识点分析这样做的好处在哪里,如代码所示。

  1. public class Singleton4 {
  2. // 实例变量
  3. private byte[] data = new byte[1024];
  4. // 私有构造函数,不允许外部new
  5. private Singleton4() {
  6. }
  7. private static class Holder {
  8. private static Singleton4 instance = new Singleton4();
  9. }
  10. // 调用getInstance方法,事实上是获得Holder的instance静态属性
  11. public static Singleton4 getInstance() {
  12. return Holder.instance;
  13. }
  14. }

在Singleton类中并没有instance的静态成员, 而是将其放到了静态内部类Holder之中, 因此在Singleton类的初始化过程中并不会创建Singleton的实例, Holder类中定义了Singleton的静态变量, 并且直接进行了实例化, 当Holder被主动引用的时候则会创建Singleton的实例, Singleton实例的创建过程在Java程序编译时期收集至() 方法中, 该方法又是同步方法, 同步方法可以保证内存的可见性、JVM指令的顺序性和原子性、Holder方式的单例设计是最好的设计之一, 也是目前使用比较广的设计之一。

7.枚举方式

使用枚举的方式实现单例模式是《Effective Java》作者力推的方式, 在很多优秀的开源代码中经常可以看到使用枚举方式实现单例模式的(身影),枚举类型不允许被继承,同样是线程安全的且只能被实例化一次, 但是枚举类型不能够懒加载, 对Singleton主动使用, 比如调用其中的静态方法则INSTANCE会立即得到实例化, 读者可以自行测试, 示例代码如所示。

  1. // 枚举类型本身是final的,不允许被继承
  2. public enum Singleton5 {
  3. INSTANCE;
  4. // 实例变量
  5. private byte[] data = new byte[1024];
  6. Singleton5() {
  7. System.out.println("INSTANCE will be initialized immediately");
  8. }
  9. public static void method() {
  10. // 调用该方法则会主动使用Singleton,INSTANCE将会被实例化
  11. }
  12. public static Singleton5 getInstance() {
  13. return INSTANCE;
  14. }
  15. }

但是也可以对其进行改造, 增加懒加载的特性, 类似于Holder的方式, 改进后的代码如所示。

  1. public class Singleton6 {
  2. // 实例变量
  3. private byte[] data = new byte[1024];
  4. private Singleton6() {
  5. }
  6. // 使用枚举充当holder
  7. private enum EnumHolder {
  8. INSTANCE;
  9. private Singleton6 instance;
  10. EnumHolder() {
  11. this.instance = new Singleton6();
  12. }
  13. private Singleton6 getSingleton() {
  14. return instance;
  15. }
  16. }
  17. public static Singleton6 getInstance() {
  18. return EnumHolder.INSTANCE.getSingleton();
  19. }
  20. }