1. 模式简介
确保一个类在任何情况下都绝对只有一个实例,并提供一个全局的访问入口点;隐藏所有的构造方法;属于创建型设计模式。
2. 实现方式
2.1. 饿汉式单例
先将实例创建出来,用的时候直接拿
2.1.1. 属性初始化
/**
* <p>
* 饿汉式单例,在属性初始化
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 11:32
*/
public class EagerSingleton1 {
private final static EagerSingleton1 INSTANCE = new EagerSingleton1();
/**
* 私有化构造方法
*/
private EagerSingleton1() {
}
/**
* 提供全局访问入口
*/
public static EagerSingleton1 getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + EagerSingleton1.getInstance());
}, 10, 5);
}
}
2.1.2. 静态代码块初始化
/**
* <p>
* 饿汉式单例,在静态代码块初始化
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 11:46
*/
public class EagerSingleton2 {
/**
* 此处需要设置为 {@code final} 的,防止被后续赋值!
*/
private final static EagerSingleton2 INSTANCE;
static {
INSTANCE = new EagerSingleton2();
}
/**
* 私有化构造方法
*/
private EagerSingleton2() {
}
/**
* 提供全局访问入口
*/
public static EagerSingleton2 getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + EagerSingleton2.getInstance());
}, 10, 5);
}
}
2.2. 懒汉式单例
用的时候再去创建
2.2.1. 简单实现
/**
* <p>
* 懒汉式单例,简单实现
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:15
*/
public class LazySingletonSimple {
private static LazySingletonSimple INSTANCE = null;
private LazySingletonSimple() {
}
/**
* 此处需要加 {@code synchronized},保证线程安全
*/
public synchronized static LazySingletonSimple getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingletonSimple();
}
return INSTANCE;
}
public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + LazySingletonSimple.getInstance());
}, 10, 5);
}
}
2.2.2. 双重检查锁实现
/**
* <p>
* 懒汉式单例,双重检查锁
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:19
*/
public class LazySingletonDoubleCheck {
/**
* 添加 {@code volatile} 解决底层 CPU 指令重排的问题
*/
private volatile static LazySingletonDoubleCheck INSTANCE = null;
private LazySingletonDoubleCheck() {
}
public static LazySingletonDoubleCheck getInstance() {
if (INSTANCE == null) {
synchronized (LazySingletonDoubleCheck.class) {
if (INSTANCE == null) {
INSTANCE = new LazySingletonDoubleCheck();
}
}
}
return INSTANCE;
}
public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + LazySingletonDoubleCheck.getInstance());
}, 10, 5);
}
}
2.2.3. 静态内部类实现(推荐写法)
/**
* <p>
* 懒汉式单例,内部类写法,{@code 推荐写法之一}
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:24
*/
public class LazySingletonInnerClass {
private LazySingletonInnerClass() {
// 反射获取类的构造方法,通过newInstance() 获取对象会存在单例被破坏的问题
// 添加以下代码解决,强制不允许构建
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static LazySingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private final static LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 普通方式获取
LazySingletonInnerClass instance1 = LazySingletonInnerClass.getInstance();
// 反射方式获取
Class<LazySingletonInnerClass> clazz = LazySingletonInnerClass.class;
Constructor<LazySingletonInnerClass> constructor = clazz.getDeclaredConstructor(null);
// 设置访问级别,因为是private的
constructor.setAccessible(true);
LazySingletonInnerClass instance2 = constructor.newInstance();
System.out.println("instance1 = " + instance1);
System.out.println("instance2 = " + instance2);
}
}
2.3. 注册式单例
将每一个实例都缓存到统一的容器中,使用唯一标识获取实例
2.3.1. 枚举式单例(推荐写法)
/**
* <p>
* 注册式单例,枚举实现,{@code 推荐写法之一}
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:35
*/
public enum RegisterSingletonEnum {
/**
* 单例
*/
INSTANCE;
public static RegisterSingletonEnum getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonEnum.getInstance());
}, 10, 5);
}
}
2.3.2. 容器式单例
2.3.2.1. Spring式实现
/**
* <p>
* 注册式单例,容器实现,Spring 容器的实现
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:38
*/
public class RegisterSingletonContainer {
private static final Map<String, Object> BEAN_CACHE = new ConcurrentHashMap<>();
private RegisterSingletonContainer() {
}
public static Object getInstance(String className) {
synchronized (BEAN_CACHE) {
// 判断缓存是否存在
if (!BEAN_CACHE.containsKey(className)) {
// 构建对象,放在缓存
Class<?> aClass = null;
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
BEAN_CACHE.put(className, aClass);
return aClass;
} else {
return BEAN_CACHE.get(className);
}
}
}
public static void main(String[] args) throws InterruptedException {
ConcurrentExecutor.execute(() -> {
Object instance = RegisterSingletonContainer.getInstance("com.xkcoding.design.pattern.creational.singleton.register.RegisterSingletonContainer");
System.out.println(System.currentTimeMillis() + " :: " + instance);
}, 10, 5);
}
}
2.3.2.2. ThreadLocal式单例
ThreadLocal式单例其实也属于
容器式单例,伪线程安全,保证线程内部全部唯一,同一线程内线程安全
/**
* <p>
* 注册式单例,ThreadLocal实现,伪线程安全,保证线程内部全部唯一,同一线程内线程安全
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:55
*/
public class RegisterSingletonThreadLocal {
private static final ThreadLocal<RegisterSingletonThreadLocal> INSTANCE = ThreadLocal.withInitial(RegisterSingletonThreadLocal::new);
private RegisterSingletonThreadLocal() {
}
public static RegisterSingletonThreadLocal getInstance() {
return INSTANCE.get();
}
/**
* 测试可见,同一线程内单例,不同线程间非单例
*/
public static void main(String[] args) throws InterruptedException {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
ConcurrentExecutor.execute(() -> {
System.out.println("线程号: " + Thread.currentThread().getName() + "," + RegisterSingletonThreadLocal.getInstance());
}, 10, 5);
}
}
3. 问题点
3.1. 反序列化破坏单例的情况
测试代码:
/**
* <p>
* 饿汉式单例,测试反序列化被破坏的情况
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 20:11
*/
public class EagerSingleton3 implements Serializable {
private final static EagerSingleton3 INSTANCE = new EagerSingleton3();
private EagerSingleton3() {
}
public static EagerSingleton3 getInstance() {
return INSTANCE;
}
/**
* 重写 {@code readResolve} 方法,可解决反序列化单例破坏的场景
*/
private Object readResolve() {
return INSTANCE;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 正常获取实例
EagerSingleton3 instance1 = EagerSingleton3.getInstance();
// 通过序列化获取
EagerSingleton3 instance2 = null;
// 将 instance1 写出到文件
FileOutputStream fos = new FileOutputStream("EagerSingleton3.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close();
// 读取文件到 instance2
FileInputStream fis = new FileInputStream("EagerSingleton3.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance2 = (EagerSingleton3) ois.readObject();
ois.close();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance1 == instance2);
}
}
其实上面的代码已经给出答案了,重写 readResolve() 方法,将 INSTANCE 返回即可解决。
为什么重写 readResolve() 方法,将 INSTANCE 返回就可以解决这个单例被破坏的问题呢?跟一波源码看看 ~
// 1. 首先得明确 原因应该是反序列化为对象的时候 导致单例失败的
// 所以跟踪一波 ois.readObject() 源码 -> java.io.ObjectInputStream#readObject
// 2. debug 可以发现,执行了 readObject0(false) 返回对象
Object obj = readObject0(false);
// 3. 继续跟踪 readObject0() 的源码 -> java.io.ObjectInputStream#readObject0
// 省略部分代码,只看下面的 switch 部分代码
switch (tc) {
case TC_NULL:
return readNull();
case TC_REFERENCE:
return readHandle(unshared);
case TC_CLASS:
return readClass(unshared);
case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared);
case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared));
case TC_ARRAY:
return checkResolve(readArray(unshared));
case TC_ENUM:
return checkResolve(readEnum(unshared));
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex);
case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
}
case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
}
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
// debug可以发现反序列化的时候走的是
case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));
// 4. 因此,继续跟踪源码 readOrdinaryObject(unshared) -> java.io.ObjectInputStream#readOrdinaryObject
// 省略部分代码
obj = desc.isInstantiable() ? desc.newInstance() : null;
// 这段代码里 desc.isInstantiable() 主要是判断类是否可以被序列化和反序列化,同时是否提供一个无参的构造方法
// 这里显然为 true, 所以 obj = desc.newInstance(),这就意味着已经被新创建了一个对象
// 5. 上面只是解释了为什么反序列化会破坏单例的情况,下面我们看看怎么解决
// 接着看 java.io.ObjectInputStream#readOrdinaryObject 这个方法
// 省略部分代码
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
// 这里存在一个 if 的条件判断,desc.hasReadResolveMethod()
// 满足条件的时候,Object rep = desc.invokeReadResolve(obj);
// 如果 rep != obj 的时候呢,就把 rep 赋值给 obj, handles.setObject(passHandle, obj = rep);
// 6. 看到这里,我们就得出一个结论:
// 如果要是仍然保证单例,我们只要让 rep 返回的时候等于原先的对象覆盖给 obj 就可以实现单例了
// 7. 所以这里的关键就是 desc.hasReadResolveMethod() 和 desc.invokeReadResolve(obj) 这两个方法了
// 7.1. 那我们首先来看看 desc.hasReadResolveMethod() 这个方法
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
// 这里就只是判断 readResolveMethod 这个是否为 null,那我们就来看看 readResolveMethod 这到底是个啥吧
/** class-defined readResolve method, or null if none */
private Method readResolveMethod;
// 属性的声明说的很清楚,是一个名为 readResolve 的方法,但是返回值是啥呢,我们再找找,这个值不会凭空生成,要么是构造方法生成的时候放进去的,要么就是手动set的
// 那我们就用快捷键 Command + B 看看这个属性在哪里被用到了吧
// 不难发现,这是在 java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>) 构造方法的时候,通过反射设置的
// 省略部分代码
readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);
// 再看 java.io.ObjectStreamClass#getInheritableMethod(Class<?> cl, String name, Class<?>[] argTypes, Class<?> returnType)
// readResolveMethod 就是 函数名为 readResolve,无参,返回值是 Object 的方法
// 7.2. 再看看 desc.invokeReadResolve(obj) 这个方法 -> java.io.ObjectStreamClass#invokeReadResolve
// 这个方法就是反射调用 readResolveMethod 这个 Method 对象返回结果
// 8. 到这儿,聪明的你应该知道怎么玩儿了吧,重写 readResolve 方法,返回 INSTANCE 完事~
private Object readResolve() {
return INSTANCE;
}
注意哦,当我们使用枚举式单例的写法就不会出现这种情况,具体原因如下:
// 还是走到 上面的 switch 分支,但是枚举式单例就会走
case TC_ENUM: return checkResolve(readEnum(unshared));
// 跟踪 java.io.ObjectInputStream#readEnum 源码可以发现
// 省略部分代码
Enum<?> en = Enum.valueOf((Class)cl, name);
// 所以不会导致单例被破坏的情况~
3.2. 反射破坏单例的情况
测试代码:
/**
* <p>
* 懒汉式单例,内部类写法,{@code 推荐写法之一}
* </p>
*
* @author yangkai.shen
* @date Created in 2019-08-11 19:24
*/
public class LazySingletonInnerClass {
private LazySingletonInnerClass() {
// 反射获取类的构造方法,通过newInstance() 获取对象会存在单例被破坏的问题
// 添加以下代码解决,强制不允许构建
if (SingletonHolder.INSTANCE != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static LazySingletonInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private final static LazySingletonInnerClass INSTANCE = new LazySingletonInnerClass();
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 普通方式获取
LazySingletonInnerClass instance1 = LazySingletonInnerClass.getInstance();
// 反射方式获取
Class<LazySingletonInnerClass> clazz = LazySingletonInnerClass.class;
Constructor<LazySingletonInnerClass> constructor = clazz.getDeclaredConstructor(null);
// 设置访问级别,因为是private的
constructor.setAccessible(true);
LazySingletonInnerClass instance2 = constructor.newInstance();
System.out.println("instance1 = " + instance1);
System.out.println("instance2 = " + instance2);
}
}
其实上面的代码已经给出答案了,只需要在构造方法里判断当前实例是否已创建,已创建抛出运行时异常,即可解决。
4. 应用
// 1. ServletContext
// 2. ServletConfig
// 3. ApplicationContext
// 4. DBPool
5. 优缺点
优点: 在内存中只有一个实例,减少内存开销;可以避免对资源的多重占用;设置全局访问点,严格控制访问
缺点: 没有接口,扩展困难;扩展单例对象,只能修改代码,不符合开闭原则
6. 完整代码地址
测试代码里 ConcurrentExecutor 具体实现请看这里:https://github.com/xkcoding/design-pattern/blob/master/src/main/java/com/xkcoding/design/pattern/utils/ConcurrentExecutor.java