今天的博客主题
设计模式 ——》 设计模式之单例模式
单例模式 SP (Singleton Pattern)
概念
单例模式是设计模式中最简单的模式。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式三要素
1)单例类只能有一个实例。
2)单例类必须自己创建自己的唯一实例。
3)单例类必须给所有其他对象提供这一实例。
以此保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决一个全局使用的类频繁地创建与销毁。
优点
1)在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
2)避免对资源的多重占用(比如写文件操作)
缺点
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化
使用场景
1)要求生产唯一序列号。
2)WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3)创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
4)重量级对象,不需要多个实例,如线程池,数据库连接池
5)...
代码示例
单例模式有多种实现方式,不管哪种共同点就是:私有的构造函数,提供全局访问点
饿汉式
特点:非懒加载,线程安全,实现简单,没有锁,效率高
缺点:产生垃圾对象,浪费内存
总结:比较常用,但容易产生垃圾对象。
类加载的初始化阶段就完成了实例的初始化。本质上是借助于 jvm classloader 机制,保证实例的唯一性(初始化过程只会执行一次)以及线程安全(JVM以同步的形式来完成类加载的过程)
public class Singleton {
// 私有化构造函数
private Singleton(){}
// 类初始化时,就加载该对象。
private static Singleton singleton= new Singleton();
// 调用此方法,返回该对象
public static Singleton getSingleton() {
return singleton;
}
}
懒汉式
特点:懒加载(延迟加载),实现简单
缺点:线程不安全,不支持多线程
总结:最基本的实现方式,最大的问题就是不支持多线程,懒加载很明显,线程不安全。没有加锁,严格意义上讲它不算是单例模式
public class Singleton {
// 私有化构造函数
private Singleton (){}
// 声明该对象,但不会初始化,用到时候才会去实例化它
private static Singleton singleton;
// 调用此方法,返回该对象
public static Singleton getSingleton() {
// 对象为空,去实例化。不为空,直接返回
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
懒汉式(安全的)
特点:懒加载、线程安全,实现简单
缺点:加锁,效率低
总结:非常好的懒加载,第一次调用才会初始化,方法加锁,在多线程下可以很好的工作,效率低
public class Singleton {
// 私有化构造函数
private Singleton (){}
// 声明该对象,但不会初始化,用到时候才会去实例化它
private static Singleton singleton;
// 方法加锁,调用此方法,返回该对象
public static synchronized Singleton getSingleton() {
// 对象为空,去实例化。不为空,直接返回
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
懒汉式(double check)
特点:懒加载(延迟加载)、线程安全、JDK1.5之后
缺点:实现较复杂
总结:采用双锁机制,线程安全且能在多线程情况下保持高性能。
只有在真正使用的时候,才开始初始化
public class Singleton {
// 私有化构造函数
private Singleton (){}
// 声明该对象,但不会初始化,用到时候才会去实例化它
// volatile 关键字,防止编译器对指令重排
private volatile static Singleton singleton;
// 调用此方法,返回该对象
public static Singleton getInstance() {
// 对象为空,去实例化。不为空,直接返回
if (singleton == null) {
// 加锁校验
synchronized (Singleton.class) {
// 双重验证
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
登记式/静态内部类
特点:懒加载,线程安全,实现难度一般
缺点:
总结:这种方式能达到双检锁方式一样的功能,实现更简单。充分利用了静态内部类的特点,对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
public class Singleton {
private Singleton (){}
private static class SingletonHolder {
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.singleton;
}
}
枚举式
特点:非懒加载、线程安全、实现简单、有自己的反序列化机制,不支持反射创建,JDK1.5之后
缺点:
总结:推荐写法,简单高效。充分利用枚举类的特性,只定义了一个实例,且枚举类是天然支持多线程的。只不过还没有被广泛应用
public enum Singletons {
SINGLETON;
public void getSingleton(){}
}
不建议使用懒汉式(第2,第3种方式),建议使用饿汉式(第1种方式)
只有在明确实现懒加载效果时,使用登记式(第5种方式)
如涉及到了反序列化创建对象时,强烈建议使用最后一种方式
如果有别的要求那就考虑使用双重验证锁方式(第4种方式)
源码中的单例
Spring & JDK
java.lang.Runtime
org.springframework.aop.framework.ProxyFactoryBean
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
org.springframework.core.ReactiveAdapterRegistry
Tomcat
org.apache.catalina.webresources.TomcatURLStreamHandlerFactory
反序列化指定数据源
java.util.Currency
通过反射攻击单例