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