单例模式
目录
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
单例模式的结构与实现
单例模式是设计模式中最简单的模式之一。通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
饿汉模式(最简单的)
public class HungrySingleton {
// 类一旦加载就创建一个单例
private static HungrySingleton hungrySingleton = new HungrySingleton();
// 构造私有化,避免类在外部被实例化
private HungrySingleton() {}
// 对外提供唯一的获取途径
public static HungrySingleton getIns() {
return hungrySingleton;
}
}
懒汉式
public class LazySingleton {
private static LazySingleton lazySingleton;
// 构造私有化,避免类在外部被实例化
private LazySingleton() {}
// 对外提供唯一的获取途径
// 直接方法加锁(效率不高)
public synchronized static LazySingleton getIns() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
双重检查锁(DCL)
public class DoubleCheckSingleton {
private static DoubleCheckSingleton doubleCheckSingleton;
// 构造私有化,避免类在外部被实例化
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getIns() {
if (doubleCheckSingleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (doubleCheckSingleton == null) {
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
}
这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。
指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。
doubleCheckSingleton = new DoubleCheckSingleton()
可分解为如下伪代码:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
但是经过重排序后如下:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址
//注意,此时对象还没有被初始化!
ctorInstance(memory); //2:初始化对象
在多线程情况下可能会出现 线程A执行了instance = memory
; 此时线程B 发现 doubleCheckSingleton
不为空,随即直接返回。
可以使用volatile变量禁止指令重排序,让DCL生效:
public class DoubleCheckSingleton {
// 添加 volatile 关键字 防止指令重排序
private volatile static DoubleCheckSingleton doubleCheckSingleton;
// 构造私有化,避免类在外部被实例化
private DoubleCheckSingleton() {}
public static DoubleCheckSingleton getIns() {
if (doubleCheckSingleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (doubleCheckSingleton == null) {
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
}
静态嵌套类(静态内部类)
public class StaticNestedSingleton {
private StaticNestedSingleton() {}
// 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
private static class Holder {
private static StaticNestedSingleton instance = new StaticNestedSingleton();
}
public static StaticNestedSingleton getIns() {
return Holder.instance;
}
}
依赖注
public class DependencyInjectionSingleton {
private static ConcurrentMap<String, Object> ioc = new ConcurrentHashMap<>();
private DependencyInjectionSingleton(){}
public synchronized static Object getBean(String name) {
if (!ioc.containsKey(name)) {
try {
Class<?> clazz = Class.forName(name);
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object o = constructor.newInstance();
ioc.put(name, o);
return o;
} catch (Exception e) {
e.printStackTrace();
}
}
return ioc.get(name);
}
}
枚举
public enum EnumSingleton {
/**
* 枚举实例
*/
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getIns() {
return INSTANCE;
}
}
关于破坏单例的两种方式
序列化
类在实现了序列化的情况下,我们则可以通过序列化和反序列化达到破坏单例模式
反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成。
反射
尽管构造器私有化,我们还是可以通过反射的方式获取构造方法,实例化一个新的对象。
避免方式
使用枚举的方式,可以有效避免这种情况 。
那为什么枚举可以避免这两种破坏方式呢?
实现序列化的枚举代码:
public enum EnumSingleton implements Serializable {
/**
* 枚举实例
*/
INSTANCE, INSTANCE_1;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getIns() {
return INSTANCE;
}
}
通过JAD反编译工具,看看实际的源码
public final class EnumSingleton extends Enum
implements Serializable
{
// 反序列化会调用这个方法 返回新的数组。实例引用则是相同的实例
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(create/singleton/enums/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public Object getData()
{
return data;
}
public void setData(Object data)
{
this.data = data;
}
public static EnumSingleton getIns()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
public static final EnumSingleton INSTANCE_1;
private Object data;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
INSTANCE_1 = new EnumSingleton("INSTANCE_1", 1);
$VALUES = (new EnumSingleton[] {
INSTANCE, INSTANCE_1
});
}
}
实际上在使用关键字enum
创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API
中的java.lang.Enum
类,也就是说通过关键字enum
创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum
类
public static final EnumSingleton INSTANCE;
public static final EnumSingleton INSTANCE_1;
private static final EnumSingleton $VALUES[];
static
{
// 类加载是就初始化
INSTANCE = new EnumSingleton("INSTANCE", 0);
INSTANCE_1 = new EnumSingleton("INSTANCE_1", 1);
$VALUES = (new EnumSingleton[] {
INSTANCE, INSTANCE_1
});
}
这段代码,我们可以看出来枚举使用的饿汉式模式来实例化的,这是在jvm层面上替我们实现的,相信效率会更高效。
说了半天还是没有说到点子上,别慌:happy: 。我们接着来
反序列化是通过 ObjectOutputStream.readObject
实例对象的。我们点进去看看
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
// 在这儿,我们继续往下看
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
private Object readObject0(boolean unshared) throws IOException {
// 。。。省略
// 我们只看和枚举有关的
case TC_ENUM:
// 在接着
return checkResolve(readEnum(unshared));
// 。。。省略
}
private Enum<?> readEnum(boolean unshared) throws IOException {
// 。。。省略
// 找到获取枚举的方法了,还得继续呀。。
Enum<?> en = Enum.valueOf((Class)cl, name);
// 。。。省略
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
// 看这个 enumConstantDirectory
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
Map<String, T> enumConstantDirectory() {
if (enumConstantDirectory == null) {
// 这个就是存储实例的数组了吧,看见曙光了。接着往下看吧
T[] universe = getEnumConstantsShared();
if (universe == null)
throw new IllegalArgumentException(
getName() + " is not an enum type");
Map<String, T> m = new HashMap<>(2 * universe.length);
for (T constant : universe)
m.put(((Enum<?>)constant).name(), constant);
enumConstantDirectory = m;
}
return enumConstantDirectory;
}
// 终于是让我们找到了
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
// 看到这儿没,反编译中枚举的方法 通过反射调用的
// 到此我们,就回到最开始反编译代码中的 values()方法了
// return $VALUES.clone() 数组对象
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}
通过这个代码流程,可以得到。反序列化最终得到还是,最开始初始化的实例
try {
Class<?> clazz = EnumSingleton.getIns().getClass();
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
// 反编译知道构造器,两个参数分别代表 实例对象的引用名称,和位置。
Object o = constructor.newInstance("INSTANCE", 0);
System.out.println(o);
System.out.println(EnumSingleton.getIns());
} catch (Exception e) {
e.printStackTrace();
}
// 我们再接着看吧
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 咯,看到没,看到没。在jdk层面就已经不允许我们通过反射构造枚举对象了
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}