1 基本介绍
单例模式简单来说就是不用创建多个实例
单例模式有三个要点:
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 必须自行向整个系统提供这个实例
这是最简单的设计模式,核心结构只有一个包含称为单例类的特殊类
下面就直接看看单例的几种实现方式把
2 设计分析
2.1 饿汉式单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package DesignPatterns.ShangGuiGu.singleton.type1;
public class SingletonTest01 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } }
class Singleton { private Singleton() { }
private final static Singleton instance = new Singleton();
public static Singleton getInstance() { return instance; } }
|
2.2 懒汉式单例(线程不安全)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package DesignPatterns.ShangGuiGu.singleton.type3;
public class SingletonTest03 { public static void main(String[] args) { Singleton instance = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } }
class Singleton { private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
2.3 懒汉式单例(线程安全DCL)✨
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package DesignPatterns.ShangGuiGu.singleton.test.test3;
public class A { public static void main(String[] args) { B b1 = B.getSingleton(); B b2 = B.getSingleton(); System.out.println(b1 == b2); } }
class B { private volatile static B b; public static B getSingleton() { if (b == null) { synchronized (B.class) { b = new B(); } } return b; } }
|
其实可以直接在方法上加锁保证线程安全,但这样导致系统性能降低,使用双重检查锁机制更加适合。注意,这里要使用volatile
关键字,volatile关键字会屏蔽Java虚拟机所做的一些代码优化。
2.4 IoDH✨
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package DesignPatterns.ShangGuiGu.singleton;
public class Singleton { private static class HolderClass { private final static Singleton instance = new Singleton(); }
public static Singleton getInstance() { return HolderClass.instance; }
public static void main(String[] args) { Singleton s1, s2; s1 = Singleton.getInstance(); s2 = Singleton.getInstance(); System.out.println(s1 == s2); } }
|
由于静态单例对象没有作为Singleton
的成员变量直接实例化,因此类加载时不会实例化Singleton
,第一次调用getInstance()
时将加载内部类HolderClass
,在该内部类中定义了一个static
类型的变量instance
,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()
方法没有任何线程锁定,因此其性能不会造成任何影响。
通过IoDH,既可以实现延迟加载,还可以保证线程安全,不影响系统性能,是最好的实现方式
3 总结
优缺点分析
优点:
- 单例模式提供了对唯一实例的受控访问
- 节约系统资源
- 允许可变数目的实例
缺点:
- 没有抽象层,因此单例类的扩展有很大的困难
- 单例类的职责过重,在一定程度上违背了“单一职责原则”
- 如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
适用场景
- 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
4 场景使用
4.1 Spring中的单例模式
Spring的依赖注入(包括lazy-init方式)都是发生在 AbstractBeanFactory 的 getBean 里。 getBean 的 doGetBean 方法调用 getSingleton 进行bean的创建。lazy-init方式(lazy-init=“true”),在用户向容器第一次索要bean时进行调用;非lazy-init方式(lazy-init=“false”),在容器初始化时候进行调用。
同步线程安全的单例核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "'beanName' must not be null"); synchronized (this.singletonObjects) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { try { singletonObject = singletonFactory.getObject(); } catch (BeanCreationException ex) { } finally { } addSingleton(beanName, singletonObject); } return (singletonObject != NULL_OBJECT ? singletonObject : null); } }
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
} } }
|
Spring 对 Bean
实例的创建是采用单例注册表的方式进行实现的,而这个注册表的缓存是 ConcurrentHashMap
对象。
使用该方式的原因是方便继承。单例注册表的构造函数是protected
,可以继承。
参考
Java 静态内部类的加载时机 - は問わない - 博客园 (cnblogs.com)
(28 封私信) 为什么Spring使用注册表来实现单例模式? - 知乎 (zhihu.com)–木木甫回答