Java并发编程中的设计模式解析(二)一个单例的七种写法

时间:2021-01-14 17:30:26

Java单例模式是最常见的设计模式之一,广泛应用于各种框架、中间件和应用开发中。单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程、类的加载等知识,系统地介绍一下单例模式的演变,并体现在7种不同的单例设计中。说到这个,非常像孔乙己里那个“回字有四种写法”的梗,不过与封建迂腐文人不同的是,从简单的单例设计变化,可以看到一个需求演变的过程,看到一个方法不断完善的过程。

传送门:Java并发编程中的设计模式解析(一)

1. 饿汉式

最简单的单例设计,优点是线程安全,但是因为类加载即初始化实例,加入实例变量比较多的话,会占用较多的内存。

 1 //不允许被继承
 2 public final class SingletonStarve {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象的时候直接初始化
 7     private static SingletonStarve instance = new SingletonStarve();
 8     //私有化构造函数, 不允许直接new对象
 9     private SingletonStarve() {}
10     //提供公共的方法获取实例对象
11     public static SingletonStarve getInstance() {
12         return instance;
13     }
14 }

2. 懒汉式

实现了单例设计的懒加载,节省了前期内存空间的占用,但是在多线程环境下可能会导致多对象的产生,破坏实例唯一性。

 1 //不允许被继承
 2 public final class LazySingleton {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象, 不直接初始化
 7     private static LazySingleton instance = null;
 8     //私有化构造函数, 不允许直接new对象
 9     private LazySingleton() {}
10     //提供公共的方法获取实例对象
11     public static LazySingleton getInstance() {
12         if(null == instance) {
13             instance = new LazySingleton();
14         }
15         return instance;
16     }
17 }

3. 懒汉式+同步锁

通过使用synchronized关键字使getInstance方法变为同步方法,从而确保线程安全,但带来了一定的性能问题。

 1 //不允许被继承
 2 public final class SyncLazySingleton {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象, 不直接初始化
 7     private static SyncLazySingleton instance = null;
 8     //私有化构造函数, 不允许直接new对象
 9     private SyncLazySingleton() {}
10     //提供公共的方法获取实例对象, 通过synchronized修饰为同步方法
11     public static synchronized SyncLazySingleton getInstance() {
12         if(null == instance) {
13             instance = new SyncLazySingleton();
14         }
15         return instance;
16     }
17 }

4. Double-Check

推荐使用:Double-Check单例模式,通过两次非空判断,并且对第二次判断加锁,确保了多线程下的单例设计安全,同时保证了性能。

注意:Double-check有可能因为JVM指令重排的原因,导致空指针异常;使用volatile修饰对象引用,可以确保其可见性,避免异常

 1 //不允许被继承
 2 public final class VolatileDoubleCheckSingleton {
 3     //实例变量, 由于单例对象是静态的, 在类的加载阶段, 就会初始化实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //定义静态实例对象, 不直接初始化
 7     //通过volatile, 避免指令重排序导致的空指针异常
 8     private static volatile VolatileDoubleCheckSingleton instance = null;
 9     Connection conn;
10     Socket socket;
11     //私有化构造函数, 不允许直接new对象
12     //由于指令重排序, 实例化顺序可能重排, 从而导致空指针,使用volatile关键字修饰单例解决
13     private VolatileDoubleCheckSingleton() {
14         //this.conn;
15         //this.socket;
16     }
17     //提供公共的方法获取实例对象
18     public static VolatileDoubleCheckSingleton getInstance() {
19      20         if(null == instance) {
21             synchronized(VolatileDoubleCheckSingleton.class) {
22                 if(null == instance) {//以下赋值因为不是原子性的,如果不使用volatile使instance在多个线程中可见,将可能导致空指针
23                     instance = new VolatileDoubleCheckSingleton();                    
24                 }
25             }
26         }
27         return instance;
28     }
29 }

5. 静态内部类

推荐使用:通过使用静态内部类,巧妙地避免了线程不安全,并且节省了前期内存空间,编码非常简洁。

 1 //不允许被继承
 2 public final class HolderSingleton {
 3     //实例变量
 4     @SuppressWarnings("unused")
 5     private byte[] data = new byte[1024];
 6     //私有化构造器
 7     private HolderSingleton() {}
 8     //定义静态内部类Holder, 及内部实例成员, 并直接初始化
 9     private static class Holder{
10         private static HolderSingleton instance = new HolderSingleton();
11     }
12     //通过Holder.instance获得单例
13     public static HolderSingleton getInstance() {
14         return Holder.instance;
15     }
16 }

 

6. 枚举类

《Effective Java》中推荐的单例设计模式,缺点是饿汉式,并且对编码能力要求较高。

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

7. 内部枚举类

 1 /*
 2  * 使用枚举类作为内部类实现懒加载
 3  */
 4 public final class LazyEnumSingleton {
 5     private LazyEnumSingleton(){}
 6     private enum EnumHolder{
 7         INSTANCE;
 8         private LazyEnumSingleton instance;
 9         EnumHolder(){
10             this.instance = new LazyEnumSingleton();
11         }
12         private LazyEnumSingleton getLazyEnumSingleton() {
13             return instance;
14         }
15     }
16     public static LazyEnumSingleton getInstance() {
17         return EnumHolder.INSTANCE.getLazyEnumSingleton();
18     }
19 }