单例模式的概念、单例模式的结构、单例模式的优缺点、单例模式的使用场景、单例模式的实现示例、序列化和反射与单例模式、单例模式源码分析
单例模式,即只能生成一个实例的模式叫做单例模式。单例模式是只有一个简单的类,没有复杂的调用和接口的设计,只要求这个类无论什么时候只能生成一个实例即可。

如日志应用、应用配置读取、通信、线程池、windows 的任务管理器、垃圾回收站等。
单例模式一般有七种实现方式,即懒汉-线程不安全、懒汉-线程安全、饿汉、饿汉-变种、静态内部类、枚举、双重校验锁。
懒汉-线程不安全
public class SingletonOne {
private static SingletonOne singletonOne;
private SingletonOne() {}
public static SingletonOne getInstance() {
if (singletonOne == null) {
singletonOne = new SingletonOne();
}
return singletonOne;
}
}
懒汉-线程安全
public class SingletonTwo {
private static SingletonTwo singletonTwo;
private SingletonTwo() {}
public static synchronized SingletonTwo getInstance() {
if (singletonTwo == null) {
singletonTwo = new SingletonTwo();
}
return singletonTwo;
}
}
饿汉
public class SingletonThree {
private static SingletonThree singletonThree = new SingletonThree();
private SingletonThree() {}
public static SingletonThree getInstance() {
return singletonThree;
}
}
饿汉-变种
public class SingletonFour {
private static SingletonFour singletonFour;
static {
singletonFour = new SingletonFour();
}
private SingletonFour() {}
public static SingletonFour getInstance() {
return singletonFour;
}
}
静态内部类
public class SingletonFive {
private static class SingletonFiveHandler {
private static final SingletonFive INSTANCE = new SingletonFive();
}
private SingletonFive() {}
public static SingletonFive getInstance() {
return SingletonFiveHandler.INSTANCE;
}
}
枚举
public enum SingletonSix {
INSTANCE;
SingletonSix() {}
}
双重校验锁
public class SingletonSeven {
private static SingletonSeven singletonSeven;
private SingletonSeven() {}
public static SingletonSeven getInstance() {
if (singletonSeven == null) {
synchronized (SingletonSeven.class) {
if (singletonSeven == null) {
singletonSeven = new SingletonSeven();
}
}
}
return singletonSeven;
}
}
首先,在单例模式中,单例类的构造方法是私有的,也就是说不允许外界自调用单例类的构造方法创建单例对象。其次,单例模式自始至终只能有一个实例。而在反射中,实际上是通过 Singleton.class 的方式获取到类的 Class 对象,然后获取其构造器,接着调用其构造器创建该类的实例(注意,若构造器是私有的,则可通过获取到的构造器对象,设置其访问权限,然后进行调用)。所以,反射破坏了单例模式,因为如果多次通过反射方式获取一个单例类的实例就相当于多次调用该单例类的构造器创建对象,那得倒的对象必然是不同的,必然破坏了单例模式。同时,因为反序列化的本质是反射,所以反序列化也会破坏单例模式。
解决反序列化破坏单例模式
// 在单例类中加入此方法会解决此问题
public Object readResolve() {
return singletonOne;
}
java 为反序列化提供了输入流类,在反序列化时,会去判断当前类是否有 readResolve 方法,若有,则调用它。因为我们添加了 readResolve 方法,且其返回的是单例类中维护的静态单例类实例,所以就能保证每次反序列化都能得到同一个实例。同时,readResolve 方法也是 jdk 为我们提供的自定义反序列化要重写的方法。
解决反射破坏单例模式
// 单例类中维护一个表示是否是第一次创建单例对象的状态
private static Boolean flag = false;
private SingletonOne() {
// 加锁是为了防止在多线程环境下出现线程安全问题
synchronized (SingletonOne.class) {
if (flag) { // 若单例对象已经存在 则抛出异常
throw new RuntimeException("单例类 不能创建多个对象");
}
flag = true;
}
}
因为反射的本质是调用类的构造方法创建对象,所以我们可以在类的构造方法中对单例类的实例的创建进行控制,保证其自始至终只能创建一个实例。
首先,枚举类型为什么可以实现单例模式
在程序中定义的枚举类型在被编译时,JVM 最终会将其转化成一个 java.lang.Enum 类,也就是枚举类型最终会被转化成类,然后被 JVM 记载,又因为 JVM 的类加载是线程安全的,也就是一个只会被加载一次,所以最后也只有一个枚举实例,所以枚举类型可以实现单例模式。
其次,枚举实现的单例模式为什么不会被反序列化破坏
这是因为枚举类型的反序列化是定制的,其序列化时是将枚举值的 name 输出到结果中,反序列化时也是直接拿到 name,所以其自始至终都是同一个实例;再者,枚举类型的序列化和反序列化的相关方法不能被重写(也就是 writeObject、readObject 方法等),所以,其实现的单例模式不能被反序列化破坏。
最后,枚举实现的单例模式为什么不会被反射破坏
这是因为在 Class 类中为枚举类型专门定义了获取其枚举值的方法,该方法实际上是直接调用了枚举类的 value 方法,从而获取到枚举值,并不是调用其构造方法,所以,其实现的单例模式不能被反射破坏。
java.lang.Runtime 类使用了单例模式,且是用饿汉方式实现的。每个 java 程序都启动了一个 JVM 进程,此实例是由 JVM 实例化的,因此 ,每个 java 程序都有且仅有一个 Runtime 实例。用来在程序运行时与环境交互。
public class Runtime {
private static final Runtime currentRuntime = new Runtime();
private static Version version;
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class {@code Runtime} are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the {@code Runtime} object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
java.awt.Toolkit 类使用了单例模式,且是用懒汉-线程安全的方式实现的。
public abstract class Toolkit {
private static Toolkit toolkit;
public static synchronized Toolkit getDefaultToolkit() {
if (toolkit == null) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
Class<?> cls = null;
String nm = System.getProperty("awt.toolkit");
try {
cls = Class.forName(nm);
} catch (ClassNotFoundException e) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
if (cl != null) {
try {
cls = cl.loadClass(nm);
} catch (final ClassNotFoundException ignored) {
throw new AWTError("Toolkit not found: " + nm);
}
}
}
try {
if (cls != null) {
toolkit = (Toolkit)cls.getConstructor().newInstance();
if (GraphicsEnvironment.isHeadless()) {
toolkit = new HeadlessToolkit(toolkit);
}
}
} catch (final ReflectiveOperationException ignored) {
throw new AWTError("Could not create Toolkit: " + nm);
}
return null;
}
});
if (!GraphicsEnvironment.isHeadless()) {
loadAssistiveTechnologies();
}
}
return toolkit;
}
}