文章目录
单例模式(创建型模式)
1. 单例模式介绍
一个类在全局仅创建一个实例,允许在多线程访问时仅返回同一个实例
2. 单例模式原则
- 私有构造
- 使用静态方法或枚举
- 考虑多线程情况
- 考虑反射、序列化、克隆的情况
3. 场景案例
- Spring 默认单例 bean 的使用
- 数据库的连接池使用单例模式
- 业务中设置全局的属性保存:项目中使用 Jedis 的单例来连接访问 Redis
4. 好处
- 减少频繁创建及销毁的资源消耗
- 减少 new 次数,也可减少 GC 操作
5. 七种源码
1. 静态类使用(线程安全、非懒加载)
public class Singleton_00 {
/**
* 我们业务开发经常使用,没有懒加载,在第一次运行的时候就直接初始化 cache
*/
public static Map<String, Object> cache = new ConcurrentHashMap<>();
}
2. 懒汉模式(线程安全、懒加载)
- 锁定整个方法效率低下,不建议使用
public class Singleton_01 implements Cloneable,Serializable {
private static final long serialVersionUID = -6240753600251410378L;
// 不能直接调用静态变量,因为使用的是懒加载
private static Singleton_01 instance;
/**
* 私有构造器,不允许外部创建
* 解决反射破坏单例模式
*/
private static int count = 0;
private Singleton_01() {
synchronized (Singleton_01.class) {
if (count > 0) {
throw new RuntimeException("单例对象被反射破坏");
}
count++;
}
}
/**
* 添加 synchronized 锁整个方法,
* 仅在第一次、第一个线程调用此方法时才创建对象
*/
public static synchronized Singleton_01 getInstance() {
if (null != instance) {
return instance;
}
instance = new Singleton_01();
return instance;
}
@Override
public Singleton_01 clone() throws CloneNotSupportedException {
// return (Singleton_01) super.clone();
// 解决克隆破坏单例模式
return instance;
}
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
public Object readResolve() {
return instance;
}
}
3. 饿汉模式(线程安全、非懒加载)
- 不进行懒加载,大文件可能导致内存占用过大
public class Singleton_02 implements Cloneable,Serializable {
private static final long serialVersionUID = -1873983845481394404L;
/**
* 私有化成员变量、仅能通过方法获取
* 不进行懒加载,在 JVM 创建类的时候立即创建对象
*/
private static Singleton_02 instance = new Singleton_02();
/**
* 私有化构造器,不允许外部构造
*/
private Singleton_02 (){
}
public static Singleton_02 getInstance() {
return instance;
}
@Override
public Singleton_02 clone() throws CloneNotSupportedException {
return (Singleton_02) super.clone();
}
}
4. 双重锁校验-DCL(线程安全、懒加载)
public class Singleton_03 implements Cloneable,Serializable {
private static final long serialVersionUID = -7783438578663397638L;
/**
* 懒汉模式而使用懒加载
* volatile 防止实例化时,指令重排导致多线程异常
* (因为 new 非原子操作,但是 volatile 不是将其变成原子操作,而是禁止指令重排,
* 因为 new 主要分为三步,1-分配空间、2-初始化、3-引用指向对象,如果某个线程指令重排为 132,则另一个线程可能返回 null)
*/
private static volatile Singleton_03 instance;
/**
* 私有化构造器,不允许外部构造
*/
private Singleton_03() {
}
/**
* 在操作内部加锁,效率更高
* 但可读性较差,使用频率较低
*/
public static Singleton_03 getInstance() {
if (null != instance) {
return instance;
}
synchronized (Singleton_03.class) {
if (null == instance) {
instance = new Singleton_03();
}
}
return instance;
}
@Override
public Singleton_03 clone() throws CloneNotSupportedException {
return (Singleton_03) super.clone();
}
}
5. 静态内部类(线程安全、懒加载)
public class Singleton_04 implements Cloneable,Serializable {
private static final long serialVersionUID = 5036852687082314868L;
/**
* 即保证了线程安全,有保证了懒加载,还不需要加锁
* 原因是使用了 JVM 加载类时只允许一个线程处理的机制,并且 JVM 进行类加载也是懒加载方式(使用的时候才加载)
*/
private static class Singleton_04_inner {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04() {
}
public static Singleton_04 getInstance() {
return Singleton_04_inner.instance;
}
@Override
public Singleton_04 clone() throws CloneNotSupportedException {
return (Singleton_04) super.clone();
}
}
6. 枚举(线程安全,非懒加载,可防止反射/克隆/序列化破坏单例)
public enum Singleton_05 implements Cloneable {
/**
* 反编译可以看出:它只是语法糖,本质上继承了 Enum 类,然后将 INSTANCE 放在 static 代码块中 new
* public final class singleton.Singleton_05 extends java.lang.Enum<singleton.Singleton_05> {
* public static final singleton.Singleton_05 INSTANCE;
* static {};
* Code:
* 0: new #4 // class singleton/Singleton_05
* 3: dup
* 4: ldc #7 // String INSTANCE
*/
INSTANCE;
Singleton_05(){
}
}
7. CAS AtomicReference(线程安全、懒加载)
public class Singleton_06 implements Cloneable,Serializable {
private static final long serialVersionUID = 1729508621571327122L;
private static final AtomicReference<Singleton_06> INSTANCE = new AtomicReference<>();
private Singleton_06() {
}
/**
* 调用时才创建类的懒加载
* 使用 JUC 包的 CAS 的忙等操作,无锁化只允许一个线程去实例化
* 但是 CAS 也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中
*/
public static Singleton_06 getInstance() {
while (true) {
Singleton_06 instance = INSTANCE.get();
if (null != instance) {
return instance;
}
INSTANCE.compareAndSet(null, new Singleton_06());
}
}
@Override
public Singleton_06 clone() throws CloneNotSupportedException {
return (Singleton_06) super.clone();
}
}
6. 问题与解决方案
1. 反射破坏单例
- 上述方法中(除第一种)仅有使用 Enum 枚举的第六种方法不会被破坏,其他单例模式均会被破坏
- 测试方法代码
@Test
public void testReflex() {
try {
System.out.println("testReflex");
/**
* 都能被反射破坏单例
* 解决方法:在私有构造器中判断是否存在对象,存在则直接抛出异常
*/
reflex01();
reflex02();
reflex03();
reflex04();
reflex06();
// 枚举类反射中有判断,会抛出异常:不支持创建枚举
reflex05();
} catch (Exception e) {
e.printStackTrace();
}
}
private void reflex01() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
Singleton_01 singleton01 = Singleton_01.getInstance();
Constructor<Singleton_01> construtor = Singleton_01.class.getDeclaredConstructor();
construtor.setAccessible(true);
Singleton_01 singleton01Temp = construtor.newInstance();
System.out.println("Singleton_01");
System.out.println(singleton01);
System.out.println(singleton01Temp);
}
private void reflex02() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
Singleton_02 singleton02 = Singleton_02.getInstance();
Constructor<Singleton_02> construtor = Singleton_02.class.getDeclaredConstructor();
construtor.setAccessible(true);
Singleton_02 singleton02Temp = construtor.newInstance();
System.out.println("Singleton_02");
System.out.println(singleton02);
System.out.println(singleton02Temp);
}
private void reflex03() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
Singleton_03 singleton03 = Singleton_03.getInstance();
Constructor<Singleton_03> construtor = Singleton_03.class.getDeclaredConstructor();
construtor.setAccessible(true);
Singleton_03 singleton03Temp = construtor.newInstance();
System.out.println("Singleton_03");
System.out.println(singleton03);
System.out.println(singleton03Temp);
}
private void reflex04() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
Singleton_04 singleton04 = Singleton_04.getInstance();
Constructor<Singleton_04> construtor = Singleton_04.class.getDeclaredConstructor();
construtor.setAccessible(true);
Singleton_04 singleton04Temp = construtor.newInstance();
System.out.println("Singleton_04");
System.out.println(singleton04);
System.out.println(singleton04Temp);
}
/**
* 枚举类反射中有判断,会抛出异常:不支持创建枚举
*/
private void reflex05() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException, ClassNotFoundException {
Singleton_05 singleton05 = Singleton_05.INSTANCE;
// 枚举本质上继承 java.lang.Enum 枚举类,而枚举比较特殊不能用 super 帮助构造,因此需添加 name 与 ordinary 俩枚举类自带的参数
Constructor<Singleton_05> construtor = Singleton_05.class.getDeclaredConstructor(String.class, int.class);
construtor.setAccessible(true);
// java.lang.IllegalArgumentException: Cannot reflectively create enum objects
Singleton_05 singleton05Temp = (Singleton_05) construtor.newInstance();
System.out.println("Singleton_05");
System.out.println(singleton05);
System.out.println(singleton05Temp);
}
private void reflex06() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
InstantiationException {
Singleton_06 singleton06 = Singleton_06.getInstance();
Constructor<Singleton_06> construtor = Singleton_06.class.getDeclaredConstructor();
construtor.setAccessible(true);
Singleton_06 singleton06Temp = (Singleton_06) construtor.newInstance();
System.out.println("Singleton_06");
System.out.println(singleton06);
System.out.println(singleton06Temp);
}
- 解决方法:在私有构造器中判断是否存在对象,存在则直接抛出异常
- 解决方案代码
/**
* 私有构造器,不允许外部创建
* 解决反射破坏单例模式
*/
private static int count = 0;
private Singleton_01() {
synchronized (Singleton_01.class) {
if (count > 0) {
throw new RuntimeException("单例对象被反射破坏");
}
count++;
}
}
2. 克隆破坏单例
- 上述方法中(除第一种)仅有使用 Enum 枚举的第六种方法不会被破坏,其他单例模式均会被破坏
- 测试方法代码
@Test
public void testClone() {
try {
System.out.println("testClone");
/**
* 都会被克隆破坏,但是注意类必须实现 Cloneable 接口并重写 clone() 方法,才可以被调用
* 解决方法:重写 clone() 方法,返回已经当前对象
*/
clone01();
clone02();
clone03();
clone04();
clone06();
// 枚举类中自带 final clone() 方法,无法重写,调用也会抛出异常:不支持克隆
clone05();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
private void clone01() throws CloneNotSupportedException {
Singleton_01 singleton01 = Singleton_01.getInstance();
Singleton_01 singleton01Temp = singleton01.clone();
System.out.println("Singleton_01");
System.out.println(singleton01);
System.out.println(singleton01Temp);
}
private void clone02() throws CloneNotSupportedException {
Singleton_02 singleton02 = Singleton_02.getInstance();
Singleton_02 singleton02Temp = singleton02.clone();
System.out.println("Singleton_02");
System.out.println(singleton02);
System.out.println(singleton02Temp);
}
private void clone03() throws CloneNotSupportedException {
Singleton_03 singleton03 = Singleton_03.getInstance();
Singleton_03 singleton03Temp = singleton03.clone();
System.out.println("Singleton_03");
System.out.println(singleton03);
System.out.println(singleton03Temp);
}
private void clone04() throws CloneNotSupportedException {
Singleton_04 singleton04 = Singleton_04.getInstance();
Singleton_04 singleton04Temp = singleton04.clone();
System.out.println("Singleton_04");
System.out.println(singleton04);
System.out.println(singleton04Temp);
}
/**
* 枚举类中自带 final clone() 方法,无法重写,调用也会抛出异常:不支持克隆
*/
private void clone05() throws CloneNotSupportedException {
Singleton_05 singleton05 = Singleton_05.INSTANCE;
Singleton_05 singleton05Temp = null;
System.out.println("Singleton_05");
System.out.println(singleton05);
System.out.println(singleton05Temp);
}
private void clone06() throws CloneNotSupportedException {
Singleton_06 singleton06 = Singleton_06.getInstance();
Singleton_06 singleton06Temp = singleton06.clone();
System.out.println("Singleton_06");
System.out.println(singleton06);
System.out.println(singleton06Temp);
}
- 解决方法:重写 clone() 方法,返回已经当前对象
- 解决方案代码
@Override
public Singleton_01 clone() throws CloneNotSupportedException {
// 解决克隆破坏单例模式
return instance;
}
3. 序列化破坏单例
- 上述方法中(除第一种)仅有使用 Enum 枚举的第六种方法不会被破坏,其他单例模式均会被破坏
- 测试方法代码
@Test
public void testSerializable() {
try {
System.out.println("testSerializable");
/**
* 都会被序列化破坏,但要注意类必须实现 Serializable 接口
* 解决方法:添加 readResolve() 方法,返回当前对象,因为 ObjectInputStream.readObject() 会判断如果有此方法则直接调用此方法
*/
Serializable01();
Serializable02();
Serializable03();
Serializable04();
Serializable06();
// Enum 枚举类作为父类已实现了 Serializable 接口,允许序列化但是单例没有被破坏
Serializable05();
} catch (Exception e) {
e.printStackTrace();
}
}
private void Serializable01() throws IOException, ClassNotFoundException {
Singleton_01 singleton01 = Singleton_01.getInstance();
// 对象序列化到文件
SerializableObject(singleton01, "/Users/gusixue/Desktop/serializableTest.txt");
// 对象反序列化回来
Singleton_01 singleton01Temp = (Singleton_01) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");
System.out.println("Singleton_01");
System.out.println(singleton01);
System.out.println(singleton01Temp);
}
private void Serializable02() throws IOException, ClassNotFoundException {
Singleton_02 singleton02 = Singleton_02.getInstance();
// 对象序列化到文件
SerializableObject(singleton02, "/Users/gusixue/Desktop/serializableTest.txt");
// 对象反序列化回来
Singleton_02 singleton02Temp = (Singleton_02) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");
System.out.println("Singleton_02");
System.out.println(singleton02);
System.out.println(singleton02Temp);
}
private void Serializable03() throws IOException, ClassNotFoundException {
Singleton_03 singleton03 = Singleton_03.getInstance();
// 对象序列化到文件
SerializableObject(singleton03, "/Users/gusixue/Desktop/serializableTest.txt");
// 对象反序列化回来
Singleton_03 singleton03Temp = (Singleton_03) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");
System.out.println("Singleton_03");
System.out.println(singleton03);
System.out.println(singleton03Temp);
}
private void Serializable04() throws IOException, ClassNotFoundException {
Singleton_04 singleton04 = Singleton_04.getInstance();
// 对象序列化到文件
SerializableObject(singleton04, "/Users/gusixue/Desktop/serializableTest.txt");
// 对象反序列化回来
Singleton_04 singleton04Temp = (Singleton_04) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");
System.out.println("Singleton_04");
System.out.println(singleton04);
System.out.println(singleton04Temp);
}
/**
* Enum 枚举类作为父类已实现了 Serializable 接口
* 允许序列化但是单例没有被破坏,因为调用 toString 方法返回的是 Enum 类的 name 参数的值,参数的值序列化前后不会改变
*/
private void Serializable05() throws IOException, ClassNotFoundException {
Singleton_05 singleton05 = Singleton_05.INSTANCE;
// 对象序列化到文件
SerializableObject(singleton05, "/Users/gusixue/Desktop/serializableTest.txt");
// 对象反序列化回来
Singleton_05 singleton05Temp = (Singleton_05) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");
System.out.println("Singleton_05");
System.out.println(singleton05);
System.out.println(singleton05Temp);
}
private void Serializable06() throws IOException, ClassNotFoundException {
Singleton_06 singleton06 = Singleton_06.getInstance();
// 对象序列化到文件
SerializableObject(singleton06, "/Users/gusixue/Desktop/serializableTest.txt");
// 对象反序列化回来
Singleton_06 singleton06Temp = (Singleton_06) deserializableObject("/Users/gusixue/Desktop/serializableTest.txt");
System.out.println("Singleton_06");
System.out.println(singleton06);
System.out.println(singleton06Temp);
}
private void SerializableObject(Object object, String fileName) throws IOException {
FileOutputStream fos = new FileOutputStream(fileName);
@Cleanup
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(object);
oos.flush();
oos.close();
}
private Object deserializableObject(String fileName) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(fileName);
@Cleanup
ObjectInputStream ois = new ObjectInputStream(fis);
Object res = ois.readObject();
ois.close();
return res;
}
- 解决方法:添加 readResolve() 方法,返回当前对象,因为 ObjectInputStream.readObject() 会判断如果有此方法则直接调用此方法
- 解决方案代码
/**
* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致
*/
public Object readResolve() {
return instance;
}
参考:https://bugstack.cn/md/develop/design-pattern/2020-05-31-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E3%80%8B.html