目录
单例模式的概念
单例模式,顾名思义就是只有一个实例,并且由它自己负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
单例模式的要点
1、一个类有且仅有一个实例
这个是单例模式的的精髓所在,如何保证类的实例有且只有一个是单例模式必须要保证的,单例实现的所有方式都是围绕着这个出发点来展开的
2、必须自行创建这个实例
在java中,通常是将类的构造方法私有化,从而保证无法在外部进行创建实例
3、必须自行向整个系统提供这个实例
在java中,是通过类的静态方法获取类的实例
单例模式类图
单例模式归类
单例模式属于创建型模式的一种
单例模式的应用场景
单例模式可以说无论是在我们的生活中,还是工作中,都有许多的应用场景,例如:
1、我们使用的电脑里的回收站
2、Spring的抽象工厂
3、日常开发用的工具类
单例模式解决的问题
全局使用的类的实例被频繁的创建和销毁,如果这些类的实例创建和销毁非常消耗性能,对系统来说无疑增加了负担
单例模式的实现方式
1、饿汉式
预创建实例,不管实例有没有用到。
代码实现:
public final class SingletonEHS {
private static final SingletonEHS singleton = new SingletonEHS();
private SingletonEHS() {}
public static SingletonEHS getInstance() {
return singleton;
}
}
延迟初始化:否,由于实例是预创建的,所以没有达到延迟初始化的效果
线程安全:是,基于 classloader 机制避免了多线程的同步问题
反射破解:可破解
@Test
public void testSingletonEHS() {
try {
Constructor<? extends SingletonEHS> declaredConstructor = SingletonEHS.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingletonEHS singletonReflect = declaredConstructor.newInstance();
SingletonEHS singletonReflect1 = declaredConstructor.newInstance();
System.out.println("反射获取实例:" + singletonReflect);
System.out.println("反射获取实例_1:" + singletonReflect1);
System.out.println(singletonReflect == singletonReflect1);
} catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
反射获取实例:com.cn87.constants.test.designpattern.singleton.SingletonEHS@543c6f6d
反射获取实例_1:com.cn87.constants.test.designpattern.singleton.SingletonEHS@13eb8acf
false
序列化破解:可破解
序列化实例:com.cn87.constants.test.designpattern.singleton.SingletonDCL@75881071
反序列化实例:com.cn87.constants.test.designpattern.singleton.SingletonDCL@1efee8e7
false
2、懒汉式
实例在使用的时候才去创建。
代码实现:
public final class SingletonLHS {
private static SingletonLHS singleton;
private SingletonLHS() {}
public static SingletonLHS getInstance() {
if (singleton == null) {
singleton = new SingletonLHS();
}
return singleton;
}
}
严格意义上来讲,这种不属于单例模式,因为无法避免多线程同步问题。所以衍生了下面这种写法。
public final class SingletonLHS {
private static SingletonLHS singleton;
private SingletonLHS() {}
public static synchronized SingletonLHS getInstance() {
if (singleton == null) {
singleton = new SingletonLHS();
}
return singleton;
}
}
通过synchronized关键字解决了多线程访问getInstance方法获取多个实例的问题。但是由于synchronized锁的粒度较大,在高并发环境下会产生锁竞争问题。于是又派生出了下面的DCL单例实现。DCL意为double check lock 双检锁。
/**
* 声明final是为了不被继承
*/
public final class SingletonDCL {
/**
* volatile 防止指令重排序
*/
private static volatile SingletonDCL singleton;
/**
* 私有化构造方法,阻止在外部创建实例
*/
private SingletonDCL() {}
/**
* 提供获取唯一实例的静态方法
*/
public static SingletonDCL getInstance() {
//double check
if (singleton == null) {
//lock
synchronized (SingletonDCL.class) {
//double check
if (singleton == null) {
singleton = new SingletonDCL();
}
}
}
return singleton;
}
}
DCL单例不但避免了多线程下多个实例的创建,同时又降低了锁的粒度,在多线程情况下能保持高性能。volatile关键字的引入更是锦上添花,对单例对象有特殊要求的业务场景不妨尝试一下。
延迟初始化:是
线程安全:是
反射破解:可破解,同饿汉式
序列化破解:可破解
3、登记式/静态内部类
静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
代码实现:
public final class SingletonInnerClass {
private static class SingletonHolder {
private static final SingletonInnerClass singleton = new SingletonInnerClass();
}
public static SingletonInnerClass getInstance() {
return SingletonHolder.singleton;
}
}
延迟初始化:是
线程安全:是
反射破解:可破解,同饿汉式
序列化破解:可破解
4、枚举
枚举的方式是比较少见的一种实现方式,但是看上面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。
代码实现:
public class SingletonEnum {
private SingletonEnum() {}
private enum Singleton {
INSTANCE;
private final Gson gson;
Singleton() {
gson = new Gson();
}
public Gson getInstance() {
return gson;
}
}
public static Gson getInstance() {
return Singleton.INSTANCE.getInstance();
}
}
延迟初始化:否
线程安全:是
反射破解:不可破解
由于枚举没有无参构造,只有如下一个构造方法
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
但是JDK反射机制内部完全禁止了用反射创建枚举实例的可能性,所以反射破解在枚举单例中是不存在的
//通过有参构造反射创建枚举会报如下异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at me.lmagics.singleton.SingletonAttack.reflectionAttack(SingletonAttack.java:38)
at me.lmagics.singleton.SingletonAttack.main(SingletonAttack.java:19)
//这是因为反射机制禁止创建枚举类型
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
序列化破解:不可破解
对Enum的反序列化过程中,对枚举类型有一个专门的readEnum()方法来处理
private Enum<?> readEnum(boolean unshared) throws IOException {
...
//获取枚举value的name,即INSTANCE
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
//通过Enum.valueOf根据枚举类型和枚举值的名字,获得最终的单例
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);
}
}
...
return result;
}