单例模式(Singleton Pattern)

什么是单例模式

  单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点

  1. 单例类只能有一个实例。

  2. 单例类必须自己创建自己的唯一实例。

  3. 单例类必须给所有其他对象提供这一实例。

优点

  1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例

  2. 避免对资源的多重占用

缺点

没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

实现

实现单例模式关键点在于构造函数私有化,同时又分为饿汉式,懒汉式,枚举等实现方式

饿汉式

根据具体实现方式又分为静态变量,静态常量和静态代码块等

public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance(){
return INSTANCE;
}
}
public class Singleton {
private static Singleton instance;
static {
instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance(){
return instance;
}
}

该方式的优点是写法简单,并且是线程安全的,没有加锁,执行效率会提高。

缺点是该对象在类装载载的时候就进行了实例化,没有达到懒加载的目的,容易造成内存浪费。

懒汉式

/**
* 如果同时又两个线程调用getInstance方法,同时执行if (instance == null)的判断,由于对方还未进行实例化操作,导致双方都new出了一个对象
* 多线程环境禁止使用该方式
*/
public class Singleton {
private static Singleton instance;
private Singleton (){}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 在方法级别加锁,效率低下
*/
public class Singleton {
private static Singleton instance;
private Singleton (){}

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
/**
* 多个线程同时通过if (instance == null),依旧会多次new对象
*/
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
/**
* 双重检测,线程A获得锁之后,即使线程B通过了if (instance == null),由于获取不到A正在持有的锁,进入等待。
* 使用volatile保证程序从主内存读取对象,禁止指令重排
*/
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

静态内部类

/**
* 与饿汉式同样利用类加载机制来保证初始化实例时只有一个线程,而这种方式是 Singleton 类被装载了,instance 不一定被初始化。
* 因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。
*/
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

枚举

/**
* 自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化, Effective Java 作者 Josh Bloch 提倡的方式
*/
public enum Singleton {
INSTANCE;
}

破解

反序列化

Singleton.java

import java.io.Serializable;

public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance(){
return INSTANCE;
}
}

DeserializeSingleton.java

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class DeserializeSingleton {

public static void main(String[] args) throws Exception {
String path = "本地路径";
// 任意枚举除外的单例模式
Singleton s1 = Singleton.getInstance();

//将s写入本地某个路径
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();

//从本地某个路径读取写入的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
Singleton s2 = (Singleton) ois.readObject();
System.out.println(s1); // 我的s1输出:singleton.Singleton@14ae5a5
System.out.println(s2); // 我的s2输出:singleton.Singleton@6d03e736
}

}

下面是来自ObjectInputStream类的注释

* If the deserialized object defines a readResolve method
* and the invocation of that method returns an array, then readUnshared
* returns a shallow clone of that array; this guarantees that the returned
* array object is unique and cannot be obtained a second time from an
* invocation of readObject or readUnshared on the ObjectInputStream,
* even if the underlying data stream has been manipulated.

大致意思是如果被反序列化的对象定义了readResolve方法,那么ObjectInputStream的readUnshared将返回一个对象的浅克隆,这保证了对象是唯一的并且不能通过调用ObjectInputStream的方法进行二次获得。

import java.io.ObjectStreamException;
import java.io.Serializable;

public class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance(){
return INSTANCE;
}
//反序列化定义该方法,则不需要创建新对象
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
}

再次调用DeserializeSingleton打印结果

image-20201027184110777

反射

首先要明白一点,只有枚举可以防止反射。

Singleton.java

package singleton;

public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance(){
return INSTANCE;
}
}

ReflectSingleton.java

package singleton;

import java.lang.reflect.Constructor;

public class ReflectSingleton {
public static void main(String[] args) throws Exception{
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println("=============反射破解单例===============");
// Class<?> clazz = Class.forName("singleton.Singleton"); //全路径名:包名.类名
Class<?> clazz = s1.getClass();
Constructor<?> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton s3 = (Singleton) constructor.newInstance(null);
System.out.println(s3);
}
}

输出结果:

image-20201027190137930

枚举防止反射的原理分析

首先从ReflectSingleton看出,生成新的对象关键在于constructor.newInstance(null),下面是Constructor.newInstance()方法代码

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);
}
}
// 取得类修饰符和Modifier.ENUM(0x00004000)做位与运算,如果结果不等于0,就抛出IllegalArgumentException
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;
}

那么枚举类的修饰符是什么呢?

创建枚举类

public enum Singleton {
INSTANCE;
}

获取类修饰符的值并打印

package singleton;

import java.lang.reflect.Modifier;

public class EnumModifier {
public static void main(String[] args) {
int mod = Singleton.class.getModifiers();
String modifiers = Modifier.toString(mod);
System.out.println(mod); //打印结果16401
System.out.println(modifiers); //打印结果public final
}
}

为了验证结果,使用javac 编译Singleton.java,再通过javap反编译Singleton.class(这个反编译结果并非最终结果,详细原因稍后再说

PS D:\idea_workspace\design_pattern\src\singleton> javac .\Singleton.java
PS D:\idea_workspace\design_pattern\src\singleton> javap .\Singleton.class
Compiled from "Singleton.java"
public final class singleton.Singleton extends java.lang.Enum<singleton.Singleton> {
public static final singleton.Singleton INSTANCE;
public static singleton.Singleton[] values();
public static singleton.Singleton valueOf(java.lang.String);
static {};
}
PS D:\idea_workspace\design_pattern\src\singleton>

可以看到输出是正确的,那么回到最初,clazz.getModifiers() & Modifier.ENUM的结果是大于0的,当我们使用反射去创建对象的时候,理应会抛出IllegalArgumentException,让我们实验下我们的猜想

import java.lang.reflect.Constructor;

public class ReflectSingleton {
public static void main(String[] args) throws Exception{
Singleton s1=Singleton.INSTANCE;
Singleton s2=Singleton.INSTANCE;
System.out.println(s1);
System.out.println(s2);
System.out.println("=============反射破解单例===============");
// Class<?> clazz = Class.forName("singleton.Singleton"); //全路径名:包名.类名
Class<?> clazz = s1.getClass();
Constructor<?> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton s3 = (Singleton) constructor.newInstance(null);
System.out.println(s3);
}
}

image-20201027202534412

为什么执行结果和我们的预期不一致呢?

这就回到了我提到的javap反编译结果并非最终结果(我还使用JD-GUI查看,结果基本一致),那么最终反编译结果是什么样呢?

这里要介绍一款很老的反编译工具:jad

将jad.exe放在Singleton.class的同级目录下,反编译Singleton.class

D:\idea_workspace\design_pattern\src\singleton>jad -p Singleton.class
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Singleton.java

package singleton;


public final class Singleton extends Enum
{

public static Singleton[] values()
{
return (Singleton[])$VALUES.clone();
}

public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(singleton/Singleton, s);
}

private Singleton(String s, int i)
{
super(s, i);
}

public static final Singleton INSTANCE;
private static final Singleton $VALUES[];

static
{
INSTANCE = new Singleton("INSTANCE", 0);
$VALUES = (new Singleton[] {
INSTANCE
});
}
}

可以看到jad反编译的结果里枚举类的构造函数是private Singleton(String s, int i),那么让我们改造ReflectSingleton.java并打印结果

import java.lang.reflect.Constructor;

public class ReflectSingleton {
public static void main(String[] args) throws Exception{
Singleton s1=Singleton.INSTANCE;
Singleton s2=Singleton.INSTANCE;
System.out.println(s1);
System.out.println(s2);
System.out.println("=============反射破解单例===============");
// Class<?> clazz = Class.forName("singleton.Singleton"); //全路径名:包名.类名
Class<?> clazz = s1.getClass();
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Singleton s3 = (Singleton) constructor.newInstance(String.class, int.class);
System.out.println(s3);
}
}

image-20201027204837678