【设计模式】之深入分析单例模式

本文深入探讨单例模式在多线程、多处理器环境下的安全问题及解决方案,包括懒汉模式、双重检查锁定、volatile关键字使用等内容,并介绍了单例模式在JDK及Spring框架中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文主要分析单例模式在多线程、多处理器、反射、序列化等方面的安全问题,以及在 JDK和开发框架中的应用。


ps:原本查资料看代码写了三个小时,结果笔记本抽风重启,全没了……没了……了


#概述

  • 私有的构造方法
  • 指向自己唯一实例的私有对象
  • 返回唯一实例的公共方法以供外界访问

##饿汉模式

在类加载时就初始化实例对象,线程安全

class Single{
    private static Single instance = new Single();
    private Single(){}
    public static Single getInstence(){
        return instance;
    }
}

**问题:**浪费内存

**改进办法:**懒汉模式,即惰性初始化


##懒汉模式

使用时才初始化实例对象

class Single{
    private static Single instance = null;
    private Single(){}
    public static Single getInstance(){
        if(instance == null){
            instance = new Single();
        }
        return instance;
    }
}

问题: 非线程安全

  • instance被多次实例化
  • 获取未完成初始化的对象

**改进办法:**对方法加同步锁

class Single{
    private static Single instance = null;
    private Single(){}
    public static synchronized Single getInstance(){
        if(instance == null){
            instance = new Single();
        }
        return instance;
    }
}

问题: synchronized 是重量级锁,效率低

**改进办法:**对代码块加同步锁

class Single{
    private static Single instance = null;
    private Single(){}
    public static Single getInstance(){    
	    if(instance == null){
		    synchronized(Single.class){
	            instance = new Single();
	        }
		}
        return instance;
    }
}

问题: 线程不安全,同不加锁

**改进办法:**二次检查

class Single{
    private static Single instance = null;
    private Single(){}
    public static Single getInstence(){    
	    if(instance == null){   //1
		    synchronized(Single.class){
			    if(instance == null){  //2
			      instance = new Single();  //3
			    }
	        }
		}
        return instance;
    }
}

问题:
为减少阻塞,将方法锁改为对代码块加锁,但在1处可能发生instance不为空,但实例未初始化成功的现象。

  • 处理器指令乱序执行
  • 编译器指令重排序
instance = new Single();

正常顺序应是:、初始化、分配内存、赋值
执行过程可能是:分配内存、赋值、初始化

改进办法: instancevolatile关键字修饰

  • volatile保证多线程可见性,提示线程每次从共享内存中读取变量,而不是从私有内存中读取

  • 相当于插入LoadStore内存屏障,禁止指令重排序

到此为止,安全的单例模式基本实现。

为什么是基本呢?还有如下安全问题。


#其他安全问题


###反射

调用私有的构造函数

解决办法:设置变量标识,阻止多次访问


###序列化

解决办法:反序列化时,指定对象实例(会自动调用该方法)

private Object readResolve() {
        return singleton;
    }

###克隆

不实现clonable接口即可


#更好的实现


###静态内部类

  • 虚拟机会保证一个类的<clinit>() 方法在多线程环境中被正确的加锁、同步
  • JVM类加载机制:实现惰性初始化

只有new、getstatic、putstatic、invokestatic指令、反射、主类等才会触发初始化
也就是说,只有访问静态字段INSTANCE 才会触发

public class Singleton {

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

###枚举类型

public enum DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum() {
        connection = new DBConnection();
    }
    public DBConnection getConnection() {
        return connection;
    }
}  

功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化

为什么呢?

枚举变量在编译时被声明为static,保证正确初始化

Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的
valueOf() 方法来根据名字查找枚举对象。


#JDK中单例模式

###Runtime

###NumberFormat

待续


#Spring中单例模式

Spring依赖注入Bean实例默认是单例的

DefaultSingletonBeanRegistry.java

@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值