单例设计模式你真的会用吗?
Posted 91sai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例设计模式你真的会用吗?相关的知识,希望对你有一定的参考价值。
1 快速入门
应用场景:一些重量级的对象(如:数据库连接池)不需要多个实例
实现方式如下:
-
懒汉模式
需要用到时采取创建实例
-
饿汉模式
类加载的初始化阶段去创建
-
静态内部类
类加载的初始化阶段去创建+懒汉模式
1.1 懒汉模式
示例代码如下
import lombok.Data;
/**
* 懒汉模式实现单例
* @author sai
* @version 1.0
* @date 2022/3/27 14:55
*/
@Data
public class LazySingleModel
private Integer param1 = 1;
private String param2 = "1";
/**
* 使用 volatile 禁止指令重排,防止并发场景下出现空指针错误
*/
private volatile static LazySingleModel instance;
private LazySingleModel()
/**
* 获取实例方法
*/
public static LazySingleModel getInstance()
if (null == instance)
synchronized (LazySingleModel.class)
// 使用 double check 防止多线程造成重复创建多个实例
if (null == instance)
instance = new LazySingleModel();
return instance;
return instance;
public static void main(String[] args)
LazySingleModel singleModel = LazySingleModel.getInstance();
// 如果不加 volatile,在高并发场景下,下面这行代码可能会出现空指针异常
String param1 = singleModel.getParam1().toString();
System.out.println(param1 + singleModel.getParam2());
优点:
- 用到时才去加载
缺点:
- 第一次去创建单例时遇到了并发较高的话,性能较差
- 要自己保证线程安全
1.2 饿汉模式
示例代码如下
/**
* 饿汉模式
* @author sai
* @version 1.0
* @date 2022/3/27 15:07
*/
public class HungerSingleModel
private static final HungerSingleModel INSTANCE = new HungerSingleModel();
private HungerSingleModel()
public static HungerSingleModel getInstance()
return INSTANCE;
public static void main(String[] args)
HungerSingleModel instance1 = HungerSingleModel.getInstance();
HungerSingleModel instance2 = HungerSingleModel.getInstance();
System.out.println(instance1 == instance2);
优点:
- 类加载完成时就已经创建好单例对象了,就算遇到高并发请求也没事
- 实现简单
缺点:
- 有时我们 JVM 加载这个类时,不一定是要用到实例对象的,那么创建单例和类加载强捆绑一起也不是那么的合适
1.3 静态内部类实现
示例代码如下
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel
private InnerClassSingleModel()
static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();
public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;
public static void main(String[] args)
// 懒加载:第一次调用获取单例方法时才会去加载 InnerClassSingleModelHandle,这时才创建 InnerClassSingleModel 的实例对象
// ps:JVM 类加载机制也是采用懒加载方式的
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
System.out.println(instance);
就一般而言,我们写单例时比较推荐这种实现方式,因为它具有懒汉模式和饥汉模式的优点。
2 上面所有的示例代码居然是有问题的!?
下面均用静态内部类的示例代码为例子说明问题所在,以及如何修改
2.1 反射问题
证明问题所在,具体代码如下
public class InnerClassSingleModel
...
public static void main(String[] args) throws Exception
Constructor<InnerClassSingleModel> constructor = InnerClassSingleModel.class.getDeclaredConstructor();
InnerClassSingleModel instance1 = constructor.newInstance();
InnerClassSingleModel instance2 = InnerClassSingleModel.getInstance();
System.out.println(instance1 == instance2);
运行结果如下
false 说明的确可以通过反射来绕过单例。
修复代码如下
import java.lang.reflect.Constructor;
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel
private InnerClassSingleModel()
if (null != InnerClassSingleModelHandle.INSTANCE)
// 因为反射创建实例一样会走构建方法,所以可以在构建方法里面加校验逻辑
throw new RuntimeException("单例模式不允许通过反射创建实例对象");
static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();
public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;
public static void main(String[] args) throws Exception
Constructor<InnerClassSingleModel> constructor = InnerClassSingleModel.class.getDeclaredConstructor();
InnerClassSingleModel instance1 = constructor.newInstance();
InnerClassSingleModel instance2 = InnerClassSingleModel.getInstance();
System.out.println(instance1 == instance2);
2.2 序列化问题
先还原问题,步骤如下:
1、先将类序列化到磁盘,代码如下
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel implements Serializable
private InnerClassSingleModel()
if (null != InnerClassSingleModelHandle.INSTANCE)
// 因为反射创建实例一样会走构建方法,所以可以在构建方法里面加校验逻辑
throw new RuntimeException("单例模式不允许通过反射创建实例对象");
static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();
public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;
public static void main(String[] args) throws Exception
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
oos.writeObject(instance);
生成了一个序列化文件,如下图
2、通过序列化文件读取类的字节流,生成对象还原问题
public class InnerClassSingleModel implements Serializable
...
public static void main(String[] args) throws Exception
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleModel instance2 = (InnerClassSingleModel) ois.readObject();
System.out.println(instance == instance2);
运行结果如下
修复代码如下
package com.czl.java.demo.designmodel;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel implements Serializable
/**
* 这个是为了兼容版本使用。如现在我有两个成员变量,日后我这个类被修改了,也能做兼容
* 举个例子:如我现在有 param1 和 param2,我序列化好了。回头我删掉一个字段或者增加个字段,有 serialVersionUID 的话反序列化时一样能够兼容,
* 否则会抛异常
*/
private static final long serialVersionUID = -3511142029087501481L;
/**
* 这两个字段只是为了说明 serialVersionUID 的作用
*/
private Integer param1 = 1;
private String param2 = "1";
public Integer getParam1()
return param1;
public void setParam1(Integer param1)
this.param1 = param1;
public String getParam2()
return param2;
public void setParam2(String param2)
this.param2 = param2;
/**
* 解决反序列化绕开单例问题,需要增加这个方法,修饰符可以自定义
*/
private Object readResolve()
return getInstance();
private InnerClassSingleModel()
if (null != InnerClassSingleModelHandle.INSTANCE)
// 因为反射创建实例一样会走构建方法,所以可以在构建方法里面加校验逻辑
throw new RuntimeException("单例模式不允许通过反射创建实例对象");
static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();
public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;
public static void main(String[] args) throws Exception
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleModel instance2 = (InnerClassSingleModel) ois.readObject();
System.out.println(instance == instance2);
以上是关于单例设计模式你真的会用吗?的主要内容,如果未能解决你的问题,请参考以下文章
Unittest 框架之测试固件-----(setUp与tearDown)你真的会用吗?