设计模式-单例设计模式

551 阅读3分钟
	提到常用的设计模式的时候,脑子中会蹦出很多设计模式,单例,工厂,监听者等等,经典的设计模式有23种,但是如果说对单例都不了解的话。说对其他设计模式有很深的见解就不会让人信服。现在我们就从单例聊一聊设计模式!

单例设计模式的概念

1. 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

单例设计模式的实现

	单例模式的实现最常见的就是饿汉式和懒汉式。 但是其实除了这两种实现方式还有其他的实现方式!
  1. 饿汉式: 在类加载的时候,实例就会初始化完成。
public class Singleton {
    private static final Singleton singleton = new Singleton();

    private Singleton (){}

    /**
     * 获取实例对象
     * @return 实例对象
     */
    public static Singleton getInstance(){
        return singleton;
    }
}
  1. 懒汉式: 懒汉式相对于饿汉式的优势是支持延迟加载
/**
 * 单例模式(懒汉式)
 */
public class Singleton {
    private  static Singleton singleton;

    private Singleton (){}

    /**
     * 获取实例对象
     * @return 实例对象
     */
    public static Singleton getInstance(){
        if (singleton == null) {
            synchronized(Singleton.class) { // 此处为类级别的锁
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
  • tips: 这种实现方式有些问题。因为指令重排序,可能会导致还没来得及初始化,就被另一个线程使用了.解决办法是加上 volatile 关键字。实际上,高版本的 Java 已经在JDK 内部实现中解决了这个问题。

  • 使用分析:

    • 饿汉式,因为不支持延迟加载,会使实例占用资源多或初始化耗时长。
    • 懒汉式,相对于饿汉式的优势是支持延迟加载。
    • 比较, 个人比较倾向使用饿汉式。原因是,如果因为消耗资源或者加载资源消耗时间过长,会影响系统性能,导致超时,或者OOM,这样的情况在程序启动之初我们有办法进行解决,如果在生产中可能会导致生产事故。
  1. 静态内部类
/**
 * 单例模式(静态内部类)
 */
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }

    /**
     * @return 实例对象
     */
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

}
  • 外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
  1. 枚举(enum)
/**
* 单例模式(枚举)
*/
public enum Singleton {
   INSTANCE;
}

单例模式的应用

这里主要说一下框架中单例模式是怎么实现的
  • tips: Spring依赖注入Bean实例默认是单例的.
  • Spring的依赖注入的AbstractBeanFactory 的 getBean中
	@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

接下来看下doGetBean(name, null, null, false);

/**
	 * Return the (raw) singleton object registered under the given name.
	 * <p>Checks already instantiated singletons and also allows for an early
	 * reference to a currently created singleton (resolving a circular reference).
	 * @param beanName the name of the bean to look for
	 * @param allowEarlyReference whether early references should be created or not
	 * @return the registered singleton object, or {@code null} if none found
	 */
	@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;
	}

单例模的区别

  1. 线程内单例
  • 在每个线程内都会创建一个新的实体,在java 中提供了ThreadLocal工具类。可轻松地实现线程唯一单例。
  • 自己进行实现:
/**
 * 线程内唯一
 */
public class Singleton {
    private static final ConcurrentHashMap<Long, Singleton> instances
            = new ConcurrentHashMap<>();

    private Singleton(){}

    /**
     * 
     * @return 获取实例对象
     */
    public static Singleton getInstance() {
        Long currentThreadId = Thread.currentThread().getId();
        instances.putIfAbsent(currentThreadId, new Singleton());
        return instances.get(currentThreadId);
    }
}

  1. 进程内单例
  • 正常实现的单例类就是进程内唯一的。
  1. 集群环境单例
  • 单例对象序列化并存储到外部共享存储区,比如redis或者文件