简说设计模式-单例模式
单例模式
核心作用 :
- 保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
应用场景:
- 网站计数器
- 数据库连接池的设计
- Spring容器 等等
模式优点:
- – 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要 比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动 时直接产生一个单例对象,然后永久驻留内存的方式来解决
- – 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计 一个单例类,负责所有数据表的映射处理
常见实现:
- 主要:
1. 饿汉式
描述: static变量会在类装载时初始化,由虚拟机保证单线程,可以省略synchronized关键字。 优点: 线程安全,调用效率高。 缺点: 不能延时加载;若只加载本类,而未调用getInstance(),则造成资源浪费! 示例:
| |
2. 懒汉式
描述: 调用
getInstance()时才创建实例(延时加载),整个方法用synchronized保证线程安全。 优点: 线程安全,调用效率不高。 但是,有利于资源利用;可以延时加载。 缺点:每次调用getInstance()方法同步,并发效率较低。
示例:
| |
- 其他:
3. 双重检测锁式(DCL)
描述: 将同步内容放到 if 内部,提高效率,仅第一次未创建时需要同步。
关键点:
instance必须声明为volatile——否则 JVM 在new的字节码层面可能发生指令重排(先分配引用、再初始化对象),导致另一个线程拿到一个尚未构造完成的对象。这是 DCL 在 JDK 1.5 之前不可靠的根源。
示例:
| |
4. 静态内部类式
描述: 外部类没有静态属性,静态内部类只在被调用时才会加载——**类加载的初始化由 JVM 通过 ClassLoader 的类初始化锁保证线程安全 **(JLS §12.4.2);
static final保证内存中只有一个实例且引用不可变。
优点: 线程安全,调用效率高,可以延时加载。
缺点: 反射和反序列化可破解以上几种单例实现方式。
示例:
| |
5. 枚举单例
描述: 枚举本身就是单例模式。由 JVM 从根本上提供保障——反射和反序列化都打不破:
- 反射:
Constructor.newInstance()检测到目标是枚举类型时,直接抛IllegalArgumentException("Cannot reflectively create enum objects")- 反序列化:枚举的反序列化由 JVM 走特殊路径(
Enum.valueOf),永远返回同一实例,根本不调用任何readObject/readResolve
优点: 线程安全,实现简单.
缺点: 不能延时加载.
示例:
| |
选择推荐:
- 单例对象 占用 资源 少,不需要 延时加载:
枚举式 好于 饿汉式
- 单例对象 占用 资源 大,需要 延时加载:
静态内部类式 好于 懒汉式
