单例模式也能玩出花
一、单例模式
1、什么是单例模式
(1)单例模式
【单例模式(Singleton Pattern):】 定义: Ensure a class has only one instance, and provide a global point of access to it. 直译:确保一个类只有一个实例,并提供对它的全局访问点(只允许通过全局访问点获取实例对象)。
(2)单例模式实现要点
一般情况下,访问类中某变量、方法: 可以通过 new 进行对象实例化,再通过 "对象名.变量名"、"对象名.方法名" 的形式获取。 可以通过 static 修饰(全局)变量、方法,再通过 "类名.变量名"、"类名.方法名" 的形式获取。可避免使用 new 进行对象实例化。 为了保证一个类只存在一个实例,应该保证其有且只有 一次 实例化 机会: 应该保证其构造方法不能在该类以外的地方被调用(防止使用 new 进行对象实例化)。 构造方法只能在该类中被调用一次。 注: 对象实例化常见方式: new、序列化、克隆、反射。 基本实现要点: 构造方法私有化(防止使用 new 进行对象实例化)。 在类的内部进行一次实例化(构造方法只能在该类中被调用一次)。 对外提供一个全局访问点(全局变量、全局方法等),可以通过 "类名.变量名" 或者 "类名.方法名" 的形式获取实例对象(避免使用 new 进行对象实例化)。 注: 反射会破坏 构造方法的私有化,需要注意,后面会介绍。 序列化、克隆 等操作可能会破坏单例模式。需要注意。
(3)使用场景
当频繁创建、销毁某个对象时,可以考虑单例模式。
当创建对象消耗资源过多时,但又经常使用时,可以考虑单例模式。
2、常见单例模式实现方式
(1)实现方式
【饿汉式:】 静态变量 静态代码块 枚举(推荐) 【懒汉式:】 静态方法 synchronized 同步方法 synchronized 同步代码块 双重检查 静态内部类
(2)饿汉式、懒汉式 区别
【基本区别:】 懒汉式 在需要使用对象的时候才进行实例化操作。 饿汉式 在类加载时完成实例化操作,可能暂时还不用该对象(占用内存)。 【饿汉式:】 核心: 饿汉式借助 JVM 的类加载机制,在 类加载的初始化阶段 完成 实例化操作。 类初始化阶段 只会执行一次,从而保证实例的唯一性 以及 线程安全。 当类被主动使用时,才会导致类的初始化。而被动使用时,不会导致类的初始化。 主动使用类的方式: 类的 main 方法被调用时。 执行 new 实例化操作时。 访问静态变量、静态方法时。 实例化子类时(先触发父类初始化)。 反射调用某类时。 JVM 类加载过程可参考: https://www.cnblogs.com/l-y-h/p/13496969.html#_label1_5 【懒汉式:】 核心: 在需要使用对象的时候才进行实例化操作。 多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。
二、饿汉式
1、实现
(1)基本说明
【核心思路:】 使用 static 关键字,借助类加载过程,进行实例的初始化。 使用 private 修饰 构造方法,保证构造方法私有化。 提供一个全局访问点(类名.变量名 或者 类名.方法名)获取对象。 【可用方式:】 静态变量 静态方法 静态代码块 【优点:】 在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。 【缺点:】 在类加载的初始化阶段完成了实例化,没有实现懒加载(Lazy Loading),可能造成内存的浪费(在不需要使用的时候被创建)。
(2)代码实现(静态变量)
public 修饰变量,直接通过 "类名.变量名" 的方式获取对象。
class HungrySingleton { // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton singleton = new HungrySingleton(); // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { } } public class Test { public static void main(String[] args) { HungrySingleton singleton = HungrySingleton.singleton; HungrySingleton singleton2 = HungrySingleton.singleton; System.out.println(singleton == singleton2); // true,为同一个对象 } }
(3)代码实现(静态方法)
private 修饰变量,不允许通过 "类名.变量名" 的形式访问。
public 修饰方法,通过 "类名.方法名" 的方式获取对象。
class HungrySingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton = new HungrySingleton(); // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) { HungrySingleton singleton = HungrySingleton.getInstance(); HungrySingleton singleton2 = HungrySingleton.getInstance(); System.out.println(singleton == singleton2); // true,为同一个对象 } }
(4)代码实现(静态代码块)
静态代码块,只是将实例化操作 移动到 静态代码块中进行实现。
class HungrySingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行 static { singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) { HungrySingleton singleton = HungrySingleton.getInstance(); HungrySingleton singleton2 = HungrySingleton.getInstance(); System.out.println(singleton == singleton2); // true,为同一个对象 } }
(5)这就完了吗?
当然不是了,这样写只是防止了通过 new 实例化对象。
对象实例化的方式还有 反射、序列化、克隆 等操作。
这些操作是否会破坏单例模式?需要思考一下。
2、反射破坏
(1)类主动使用时,才会进行类的初始化
类只有主动使用时,才会进行初始化操作。并不一定使用到类,就会触发初始化操作。
比如:
进行反射获取私有构造方法时,并不会触发 类加载过程。
如下代码执行后,静态代码块中的 "start..." 不会输出。
import java.lang.reflect.Constructor; class HungrySingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行 static { System.out.println("start..."); singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) throws Exception { Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class; Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor(); hungrySingletonConstructor.setAccessible(true); } }
(2)反射破坏
如下代码所示,反射调用构造方法时,会进行类加载过程(输出 "start..." ),然后构建一个实例。
此时的实例对象是通过 构造方法重新创建的对象。与类加载过程中创建的对象不同。
即 反射对 单例模式造成了破坏。
import java.lang.reflect.Constructor; class HungrySingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行 static { System.out.println("start..."); singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) throws Exception { Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class; Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor(); hungrySingletonConstructor.setAccessible(true); // 此时,还不会触发 类加载过程 HungrySingleton singleton = hungrySingletonConstructor.newInstance(); // 此时,触发 类加载过程,并创建一个实例 HungrySingleton singleton2 = HungrySingleton.getInstance(); System.out.println(singleton == singleton2); // false,不为同一个对象 } }
(3)防止反射破坏(未必会生效)
在构造方法中,判断实例是否已经被创建。
类初始化过程中,会创建一个实例。即使通过反射调用构造方法,也会在实例创建之后再去调用,所以在 构造方法中进行判断,实例存在则会抛出异常。从而防止反射破坏(未必会生效,后续序列化破坏中有提到)。
import java.lang.reflect.Constructor; class HungrySingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; // 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行 static { System.out.println("start..."); singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { if (singleton != null) { throw new RuntimeException("实例已存在,不允许重复创建"); } } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) throws Exception { Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class; Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor(); hungrySingletonConstructor.setAccessible(true); HungrySingleton singleton = hungrySingletonConstructor.newInstance(); HungrySingleton singleton2 = HungrySingleton.getInstance(); System.out.println(singleton == singleton2); // true,为同一个对象 } }
3、反序列化破坏
(1)序列化、反序列化
序列化对象,并再次读取对象时(反序列化),会创建一个新的对象。
注:
序列化就是把实体对象状态按照一定的格式写入到有序字节流。
反序列化就是从有序字节流重建对象,恢复对象状态。
【反序列化核心代码:】 ObjectInputStream 中的 readOrdinaryObject() 方法 private Object readOrdinaryObject(boolean unshared) throws IOException { Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException(desc.forClass().getName(), "unable to create instance").initCause(ex); } if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (rep != obj) { handles.setObject(passHandle, obj = rep); } } return obj; } 【关注点一:(调用构造函数实例化)】 desc.isInstantiable() 如果一个 serializable/externalizable 的类可以在运行时被实例化,那么该方法就返回true。 desc.newInstance() 通过反射调用无参构造创建一个对象。 注: 此处调用的无参构造,与类本身的无参构造方法有差别。 从实际效果上看,此处仅触发了类加载,并未触发类的构造函数。与前面提到的反射有区别。 没有深入研究,有兴趣的可以帮忙解答一下。 【关注点二:(自定义对象生成策略)】 desc.hasReadResolveMethod() 如果一个 serializable/externalizable 接口的类中包含 readResolve() 方法,则返回 true。 desc.invokeReadResolve(obj) 通过反射的方式调用要被反序列化的类的 readResolve() 方法。 handles.setObject(passHandle, obj = rep) 如果 readResolve() 返回的实例与构造方法创建的不同,则以 readResolve() 方法创建的实例为准。
(2)反序列化破坏
如下代码所示,通过反序列化创建了个对象。
从实际代码执行结果看,反序列化仅触发了类加载过程(此时调用了构造函数),反序列化中 newInstance() 未主动触发类的构造函数,所以此处构造方法中的判断 无法防止 反序列化中反射的行为。
即 反序列化对 单例模式造成了破坏。
import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.Serializable; class HungrySingleton implements Serializable { private static final long serialVersionUID = 42L; // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; static { System.out.println("start..."); // start... singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { if (singleton != null) { throw new RuntimeException("实例已存在,不允许重复创建"); } } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } } public class Test { public static void main(String[] args) throws Exception { // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test")); // oos.writeObject(HungrySingleton.getInstance()); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test")); HungrySingleton singleton = (HungrySingleton) ois.readObject(); HungrySingleton singleton2 = HungrySingleton.getInstance(); System.out.println(singleton == singleton2); // false,不为同一对象 } }
(3)防止反序列化破坏
通过 readResolve() 可以返回一个实例对象,保证此对象为类加载过程中创建的实例对象,即可防止 反序列化破坏。
import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.Serializable; class HungrySingleton implements Serializable { private static final long serialVersionUID = 42L; // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; static { System.out.println("start..."); // start... singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { if (singleton != null) { throw new RuntimeException("实例已存在,不允许重复创建"); } } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } // 定义 readResolve() 方法,返回类加载过程中创建的实例对象(反序列化时返回此对象) private Object readResolve() { return singleton; } } public class Test { public static void main(String[] args) throws Exception { // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test")); // oos.writeObject(HungrySingleton.getInstance()); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test")); HungrySingleton singleton = (HungrySingleton) ois.readObject(); HungrySingleton singleton2 = HungrySingleton.getInstance(); System.out.println(singleton == singleton2); // true,为同一对象 } }
4、克隆破坏
(1)克隆破坏
如下代码所示,通过克隆创建了个对象。
调用了 Object 的 clone 方法(native 方法),与反序列化类似,也没有触发 类的构造方法(应该是直接从内存中 copy 了一份)。创建了一个新的对象。
即 克隆对 单例模式造成了破坏。
class HungrySingleton implements Cloneable { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; static { System.out.println("start..."); // start... singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { if (singleton != null) { throw new RuntimeException("实例已存在,不允许重复创建"); } } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } // 重写 clone() 方法,返回 clone 对象 @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Test { public static void main(String[] args) throws Exception { HungrySingleton singleton = HungrySingleton.getInstance(); HungrySingleton singleton2 = (HungrySingleton) singleton.clone(); System.out.println(singleton == singleton2); // false,不是同一对象 } }
(2)防止克隆破坏
保证 clone() 方法返回的对象为类加载过程中创建的实例对象,即可防止 克隆破坏。
class HungrySingleton implements Cloneable { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static HungrySingleton singleton; static { System.out.println("start..."); // start... singleton = new HungrySingleton(); } // 构造器私有化(防止通过new创建实例对象) private HungrySingleton() { if (singleton != null) { throw new RuntimeException("实例已存在,不允许重复创建"); } } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static HungrySingleton getInstance() { return singleton; } // 重写 clone() 方法,返回 clone 对象 @Override public Object clone() throws CloneNotSupportedException { return singleton; } } public class Test { public static void main(String[] args) throws Exception { HungrySingleton singleton = HungrySingleton.getInstance(); HungrySingleton singleton2 = (HungrySingleton) singleton.clone(); System.out.println(singleton == singleton2); // true,是同一对象 } }
5、枚举
(1)基本说明
【基本说明:】 写个简单的 enum 类,然后反编译一下 javap -c xx.class。 可以看到底层就类似于 饿汉式 静态代码块 的写法。在类加载的初始化阶段完成实例化操作。 【优点:】 在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。 可以防止 克隆、反序列化、反射 破坏单例模式。 写法简单。
(2)反编译一下 enum 类
【EnumSingleton】 enum EnumSingleton { INSTANCE; } 【javap -c EnumSingleton.class】 final class pattern.sington.EnumSingleton extends java.lang.Enum<pattern.sington.EnumSingleton> { public static final pattern.sington.EnumSingleton INSTANCE; public static pattern.sington.EnumSingleton[] values(); Code: 0: getstatic #1 // Field $VALUES:[Lpattern/sington/EnumSingleton; 3: invokevirtual #2 // Method "[Lpattern/sington/EnumSingleton;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[Lpattern/sington/EnumSingleton;" 9: areturn public static pattern.sington.EnumSingleton valueOf(java.lang.String); Code: 0: ldc #4 // class pattern/sington/EnumSingleton 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class pattern/sington/EnumSingleton 9: areturn static {}; Code: 0: new #4 // class pattern/sington/EnumSingleton 3: dup 4: ldc #7 // String INSTANCE 6: iconst_0 7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V 10: putstatic #9 // Field INSTANCE:Lpattern/sington/EnumSingleton; 13: iconst_1 14: anewarray #4 // class pattern/sington/EnumSingleton 17: dup 18: iconst_0 19: getstatic #9 // Field INSTANCE:Lpattern/sington/EnumSingleton; 22: aastore 23: putstatic #1 // Field $VALUES:[Lpattern/sington/EnumSingleton; 26: return } 【等价于:】 public final class EnumSingleton extends Enum< EnumSingleton> { public static final EnumSingleton INSTANCE; public static EnumSingleton[] values(); public static EnumSingleton valueOf(String s); static { INSTANCE = new EnumSingleton(name, ordinal); }; }
(3)防止反射破坏
枚举类型的类,没有无参构造。默认继承 Enum 的有参构造。
【代码实现:】 import java.lang.reflect.Constructor; enum EnumSingleton { INSTANCE; } public class Test { public static void main(String[] args) throws Exception { Class<EnumSingleton> enumSingletonClass = EnumSingleton.class; Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor(String.class, int.class); enumSingletonConstructor.setAccessible(true); // newInstance 会出现异常,java.lang.IllegalArgumentException: Cannot reflectively create enum objects EnumSingleton enumSingleton = enumSingletonConstructor.newInstance(); EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE; System.out.println(enumSingleton == enumSingleton2); } } 【原因分析:】 newInstance() 方法中进行判断,若为枚举类型,则抛异常。 @CallerSensitive public T newInstance(Object ... initargs) throws IllegalArgumentException { if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); }
(4)防止克隆破坏
枚举类型的类,无法重写 clone() 方法。其父类 Enum 中定义 clone() 方法为 final 类型,不能被子类重写。
protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
(5)防止序列化破坏
序列化返回的是同一个对象,无需定义 readResolve() 方法。其执行的是另一个逻辑。
【代码实现:】 import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; enum EnumSingleton { INSTANCE; } public class Test { public static void main(String[] args) throws Exception { EnumSingleton enumSingleton = EnumSingleton.INSTANCE; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test")); oos.writeObject(enumSingleton); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test")); EnumSingleton enumSingleton2 = (EnumSingleton) ois.readObject(); System.out.println(enumSingleton == enumSingleton2); // true,是同一个对象 } } 【反序列化核心代码:】 ObjectInputStream 中的 readEnum() 方法。 读入并返回枚举常量,如果枚举类型不可解析,则返回 null。 private Enum<?> readEnum(boolean unshared) throws IOException { ObjectStreamClass desc = readClassDesc(false); int enumHandle = handles.assign(unshared ? unsharedMarker : null); String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } } handles.finish(enumHandle); passHandle = enumHandle; return result; }
三、懒汉式
1、实现
(1)基本说明
【核心思路:】 使用 private 修饰 构造方法,保证构造方法私有化。 提供一个静态的公共方法,在调用该方法时,才去创建实例对象。(全局访问点,通过 "类名.方法名" 获取对象)。 【可用方式:】 静态方法 synchronized 同步方法 synchronized 同步代码块 双重检查 静态内部类 【优点:】 懒加载,需要使用对象时才会去实例化操作,提高内存利用率。 【缺点:】 多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。
(2)代码实现(静态方法)
如下代码所示,只允许通过 "类名.方法名" 的方式获取对象。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,通过 "类名.变量名" 访问 public static FullSingleton getInstance() { if (fullSingleton == null) { fullSingleton = new FullSingleton(); } return fullSingleton; } } public class Test { public static void main(String[] args) throws Exception { FullSingleton fullSingleton = FullSingleton.getInstance(); FullSingleton fullSingleton2 = FullSingleton.getInstance(); System.out.println(fullSingleton == fullSingleton2); // true,是同一个对象 } }
(3)这就完了吗?
当然不是了,这样写只是在单线程环境下正常执行。多线程操作下,会出现多个实例。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,执行 new 实例化操作,此时便会产生多个实例对象。
如下代码所示,代码执行多次可以发现,两个线程输出的对象并不一致。
此时单例模式被破坏,线程不安全。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { if (fullSingleton == null) { fullSingleton = new FullSingleton(); } return fullSingleton; } } public class Test { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@f3f9f4b } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@26af6bb1 } }); thread.start(); thread2.start(); } }
(4)代码实现(synchronized 同步方法)
为了保证线程安全,可以使用 synchronized 关键字实现同步。
注:
synchronized 保证同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
如下代码所示,在方法上添加一个 synchronized,代码执行多次可以发现,两个线程输出的对象始终一致。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问 public static synchronized FullSingleton getInstance() { if (fullSingleton == null) { fullSingleton = new FullSingleton(); } return fullSingleton; } } public class Test { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638 } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638 } }); thread.start(); thread2.start(); } }
(5)这就完了吗?
当然不是了,虽然使用 synchronized 保证线程安全,但是这种方式锁粒度太大,可能会导致执行效率低。
(6)代码实现(synchronized 同步代码块)
如下代码所示,为了缩小 synchronized 影响范围,可以在方法内部使用同步代码块的方式实现。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { if (fullSingleton == null) { synchronized(FullSingleton.class) { fullSingleton = new FullSingleton(); } } return fullSingleton; } } public class Test { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@5eb39c2b } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@401a5cff } }); thread.start(); thread2.start(); } }
(7)这就完了吗?
当然不是了,这样写又回到了 静态方法中 提到的 线程不安全的问题上了。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,遇到 synchronized,同步执行后,仍会执行 new 操作,产生多个实例对象。
此时单例模式被破坏,线程不安全。双重检查可以解决这个问题。
2、双重检查
(1)代码实现
如下代码所示,双重检查,在 synchronized 同步代码块 的基础上,再添加一个判断。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { if (fullSingleton == null) { synchronized(FullSingleton.class) { if (fullSingleton == null) { fullSingleton = new FullSingleton(); } } } return fullSingleton; } } public class Test { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); } }); thread.start(); thread2.start(); } }
(2)这就完了吗?
当然不是了。这样写看上去是保证了线程安全,但是有个细节需要思考一下(指令重排)。
如下所示,反编译一下代码,可以看到实例化操作的相关指令。
【Test.java】 public class Test { public static void main(String[] args) { Test test = new Test(); } } 【javap -c Test.class】 public class pattern.sington.Test { public pattern.sington.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class pattern/sington/Test 3: dup 4: invokespecial #3 // Method "<init>":()V 7: astore_1 8: return } 【关注 main 函数:】 new 指令在 堆内存中为 Test 对象分配内存空间。 invokespecial 指令,执行实例初始化操作。 astore_1 指令,将栈顶引用类型值存入变量(即 使对象指向 堆内存空间)。 即分为三步: 1、分配内存空间。 2、实例初始化 3、实例指向内存空间 注: 按照常理说,1、2、3 是按照顺序执行的。 但是 JVM 会根据处理器特性,对指令进行优化(指令重排序),从而提高性能。 指令重排,意味着指令可能不会按照指定顺序执行。 【回到上例的 双重检查的代码:】 发生指令重排,new 实例化操作按照 1、3、2 的顺序执行。
假设线程 A 执行完 1、3,但 2 还未执行完,即对象已指向内存空间,但是还没有初始化。
此时线程 B 执行 getInstance() 代码,由于对象已指向内存空间,判断对象是否为 null 时返回 false, 跳过 synchronized 代码块。
此时线程 B 拿到的实例对象,由于初始化并未完成,使用对象将可能出现错误(引用逃逸)。 注: synchronized 并非原子性操作,可能发生指令重排。 使用 voliate 可以通过 内存屏障 禁止指令重排序。
(3)代码实现(voliate )
使用 voliate 修饰 变量,禁止指令重排序。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 // volatile 防止指令重排 private static volatile FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { if (fullSingleton == null) { synchronized(FullSingleton.class) { if (fullSingleton == null) { fullSingleton = new FullSingleton(); } } } return fullSingleton; } } public class Test { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c } }); thread.start(); thread2.start(); } }
3、静态内部类
(1)基本说明
静态内部类是一种结合了 饿汉模式、懒汉模式 优点的实现方式。
【核心思路:】 使用 private 修饰 构造方法,保证构造方法私有化。 在类的内部定义一个静态内部类(只有被调用时,才会被加载),并在内部类中实例化对象。 提供一个静态的公共方法,在调用该方法时,调用静态内部类。(全局访问点,通过 "类名.方法名" 获取对象)。 【优点:】 定义内部类,只有在用到的时候才回去加载,实现懒加载。 使用 static 定义内部类,利用 JVM 类加载机制保证 线程安全。
(2)代码实现
如下代码所示,定义一个静态内部类。
class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { return InnerInstance.INSTANCE; } // 定义静态内部类 private static class InnerInstance { public static final FullSingleton INSTANCE = new FullSingleton(); } } public class Test { public static void main(String[] args) throws Exception { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17 } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17 } }); thread.start(); thread2.start(); } }
(3)这就完了吗?
当然不是了。反序列化破坏、反射破坏、克隆破坏 的问题同样存在。
解决方式与 饿汉模式的解决方式类似。
(4)防止序列化破坏
重写 readResolve() 方法,返回实例对象。
import java.io.*; class FullSingleton implements Serializable { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { return InnerInstance.INSTANCE; } // 定义静态内部类 private static class InnerInstance { public static final FullSingleton INSTANCE = new FullSingleton(); } private Object readResolve() { return getInstance(); } } public class Test { public static void main(String[] args) throws Exception { FullSingleton fullSingleton = FullSingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test")); oos.writeObject(fullSingleton); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test")); FullSingleton fullSingleton2 = (FullSingleton) ois.readObject(); System.out.println(fullSingleton == fullSingleton2); } }
(5)防止克隆破坏
重写 clone() 方法,返回实例对象。
class FullSingleton implements Cloneable { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { } // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { return InnerInstance.INSTANCE; } // 定义静态内部类 private static class InnerInstance { public static final FullSingleton INSTANCE = new FullSingleton(); } @Override public Object clone() throws CloneNotSupportedException { return getInstance(); } } public class Test { public static void main(String[] args) throws Exception { FullSingleton fullSingleton = FullSingleton.getInstance(); FullSingleton fullSingleton2 = (FullSingleton) fullSingleton.clone(); System.out.println(fullSingleton == fullSingleton2); } }
(6)防止反射破坏
在构造方法中,新增一个判断。
import java.lang.reflect.Constructor; class FullSingleton { // 私有化变量,不可以通过 "类名.变量名" 的形式访问 private static FullSingleton fullSingleton; // 构造器私有化(防止通过new创建实例对象) private FullSingleton () { if (getInstance() != null) { throw new RuntimeException("实例已存在,创建失败"); } } // 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问 public static FullSingleton getInstance() { return InnerInstance.INSTANCE; } // 定义静态内部类 private static class InnerInstance { public static final FullSingleton INSTANCE = new FullSingleton(); } } public class Test { public static void main(String[] args) throws Exception { Class<FullSingleton> fullSingletonClass = FullSingleton.class; Constructor<FullSingleton> fullSingletonConstructor = fullSingletonClass.getDeclaredConstructor(); fullSingletonConstructor.setAccessible(true); FullSingleton fullSingleton = fullSingletonConstructor.newInstance(); FullSingleton fullSingleton2 = FullSingleton.getInstance(); System.out.println(fullSingleton == fullSingleton2); } }
四、举例
1、JDK中的单例模式举例(Runtime)
(1)部分源码
如下代码所示,就是 饿汉模式的 实现。
public class Runtime { private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } private Runtime() {} }