定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的设计要点:
- 构造方法私有化。
- 有指向自己实例的静态私有引用。
- 有对外提供自身实例的静态公有方法。
根据实例化对象时机的不同分为三种:
一种是饿汉式单例,一种是懒汉式单例,还有一种是枚举实现,它是饿汉式单例的一种特殊情况。
-
饿汉式单例,在单例类被加载时候,就实例化一个对象交给自己的引用。
/** * 饿汉式单例(可以使用) * * @author suvue * @date 2020/1/9 */ public class HungryStyle { private static HungryStyle instance = new HungryStyle(); private HungryStyle() { } public static HungryStyle getInstance() { return instance; } }
-
懒汉式单例,是指在调用取得实例方法的时候才会实例化对象。其实懒汉模式的单例创建有很多种,这里列举推荐使用的中l两种方式。
双重检索方式
结合了其余方式做了改进,其他方式的缺点如将同步锁加到getInstance()方法上,会导致速率很慢;而不进行双重检索(也就是不进行第二次校验),就会有线程安全问题,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
/** * 懒汉式单例(双重检索,推荐使用) * * @author suvue * @date 2020/1/9 */ public class DoubleCheckStyle { //volatile的使用是为了防止指令重排。 private static volatile DoubleCheckStyle instance = null; private DoubleCheckStyle() { } public static DoubleCheckStyle getInstance() { if (instance == null) { synchronized (DoubleCheckStyle.class) { if (instance == null) { //new对象的过程可拆解为三个过程: //1.为新对象分配内存空间 //2.将变量引用的指针指向内存地址。 //3.实例化对象的一系列过程 //假如一个线程执行了1、2,还没来得及执行3,这时为它分配的执行时间 //用完了,另一个线程进来,此时因为上一个线程执行了2,那么它会以为已经 //实例化好对象了,就开心的拿着这个单例对象执行操作去了,实际上只分配了 //内存空间和引用,而没有进行实例化,所以这个线程用的时候就会抛异常。 instance = new DoubleCheckStyle(); } } } return instance; } }
静态内部类方式
这种方式和饿汉式单例一样,都是采用类加载机制,但是不同的是,饿汉式在类加载时进行初始化,静态内部类方式在调用getInstance方法时才会实例化。
优点:兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(类的静态属性只会在第一次加载类的时候初始化,所以JVM帮助我们保证了线程的安全性)。
缺点:需要两个类去完成这一实现,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。因此创建好的单例,一旦在后期被销毁,不能重新创建。
/** * 懒汉式单例(通过静态内部类的方式实现,推荐使用) * * @author suvue * @date 2020/1/9 */ public class StaticInnerStyle { private StaticInnerStyle() { } private static class InnerInstance { private final static StaticInnerStyle INSTANCE = new StaticInnerStyle(); } public static StaticInnerStyle getInstance() { return InnerInstance.INSTANCE; } }
-
枚举式单例借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
package cn.suvue.discipline.practice.designpattern.singleton; /** * 枚举方式实现的单例 * * @author suvue * @date 2020/1/9 */ public class EnumStyle { private EnumStyle() { } private enum Singleton { /** * 单例 */ INSTANCE; private final EnumStyle instance; Singleton() { this.instance = new EnumStyle(); } private EnumStyle getInstance() { return instance; } } public static EnumStyle getInstance() { return Singleton.INSTANCE.getInstance(); } }
单例模式的优点
- 在内存中只有一个对象,节省内存空间。
- 避免频繁的创建销毁对象,可以提高性能。
使用注意事项
只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。我们的代码在反射面前就是裸奔的,它是一种非常规操作。
构造方法时私有的,因此单例类不可被继承。
多线程使用单例使用共享资源时,注意线程安全问题。
-
防止反射对单例造成破坏的方法,因为反射是基于构造方法拿到的实例,所以我们可以这么改一下:
private StaticInnerStyle() { if (getInstance()!=null){ throw new RuntimeException("调用失败"); } }
单例模式在spring中应用
spring中使用的单例模式的懒加载,但是使用的是单例注册表实现的,先来看一个小例子。
package cn.suvue.discipline.practice.designpattern.singleton;
import java.util.concurrent.ConcurrentHashMap;
/**
* 注册表实现的单例
*
* @author suvue
* @date 2020/1/9
*/
public class RegisterStyle {
private static ConcurrentHashMap<String, Object> register = new ConcurrentHashMap<String, Object>(32);
static {
RegisterStyle res = new RegisterStyle();
register.put(res.getClass().getName(), res);
}
private RegisterStyle() {
}
public static RegisterStyle getInstance(String name) {
if (name == null) {
name = "cn.suvue.discipline.practice.designpattern.singleton.RegisterStyle";
}
if (register.get(name) == null) {
try {
register.put(name, Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return (RegisterStyle) register.get(name);
}
}
上述的代码很简单,实现思路大同小异,唯一的不同是用到了ConcurrentHashMap。下面我们来看一下Spring中的应用。最经典的就是BeanFactory的获取bean的时候。
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//校验bean名是否有非法字符
final String beanName = transformedBeanName(name);
Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//...
//获取bean的实例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
//...
try {
//获取并检查bean的定义
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
//...
// 创建bean的实例 类定义是单例的情况.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
//出错了要销毁bean
destroySingleton(beanName);
throw ex;
}
});
//获取bean的实例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
//多例情况 这里不多分析
else if (mbd.isPrototype()) {
//...
}
//作用域相关代码,这里不看它
//...
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
if (requiredType != null && !requiredType.isInstance(bean)) {
//校验bean的类型是否与实际的相匹配
//...
}
return (T) bean;
}
上面是我简化过的代码,我们着重看下是单例情况,也就是getSingleton方法的具体实现。
/** 单例对象的缓存容器,key是bean的名称,value是bean的实例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...
boolean newSingleton = false;
//...
try {
//这里实际上创建一个新的对象
//因为这里的singletonFactory
//实际上传进来的是createBean(beanName, mbd, args)
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
//创建实例因为容器中已经存在了,就抛出异常,然后到容器中直接取单例对象
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
//...
if (newSingleton) {
//如果是新的实例对象,那么就添加到容器中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
下面我们看看spring是怎么往容器中放单例对象的。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//直接在同步代码块 执行put操作
//上段代码的getSingleton方法 用了一个synchronized
//本段代码中addSingleton方法 也用了一个synchronized
//并且锁的对象都是singletonObjects
this.singletonObjects.put(beanName, singletonObject);
//下面的代码可以不用看,重要的是上面这行
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
以上是我的一些粗鄙观点,希望看到本博文的大神能纠正其中的错误!万分感谢哦,以后我对单例有了新的认知了,会不断来补充更新的,也希望大神们多提提意见!