目录
1. 模式概述
1.1 定义
用于保证该类只会有一个实例,并提供一个全局访问点。
单例模式属于创建型模式,创建型模式用于 “解决对象如何创建” 的问题,其主要用于将对象的创建与使用分离开。
1.2 UML类图
类图说明:
- 1. 属性:私有(静态static修饰)
- 2. 构造函数:私有
- 3. 获取实例方法:公开(静态static修饰)
1.3 语法特点
- 1. 运用私有的构造函数以防止对象被外部实例化;
- 2. 运用static修饰获取实例属性以及获取实例的方法以便全局只有一份对应的实例;
1.4 应用场景
- 1. 当全局需要保证只有一个类对象时,比如一些系统的配置文件、日志打印对象等;
- 2. 当类对象需要被频繁创建又被销毁时,比如资源池等;当然如果该资源池后续需要扩展成多个则不适用于单例模式,反而更加适合用工厂模式。
2. 单例模式的几种实现方式
2.1 饿汉模式
public class HungrySingleton {
// 静态属性,以便类中的静态方法赋值调用
private static HungrySingleton instance = new HungrySingleton();
// 私有构造器,以防止对象被直接new
private HungrySingleton() {
}
// 供全局调用获取单例实例
public static HungrySingleton getInstance() {
return instance;
}
}
- 优点:该模式为线程安全,实现方便;
- 缺点:不支持懒加载,在类加载时则创建对应实例,可能会造成空间资源的浪费。
2.2 懒汉模式
public class SingletonLazy {
private static SingletonLazy instance;
// 1. 私有的构造方法
private SingletonLazy() {
}
// 2. 供外部获取类实例的静态方法
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) { // 类锁
// DCL:双层锁机制
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
// 方式二:整个方法进行同步处理,性能比较低,对性能要求比较高的不建议使用
public synchronized static SingletonLazy getInstance2() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
代码说明:延迟加载/懒汉模式
- DCL(双重锁机制):当同时有多个线程运行到第一个 if (instance == null) 这一步,也就是说多个线程已经判断当前实例没有被实例化,在准备进入下一步时,synchronized将当前类引用阻塞了,当该线程创建完实例后释放线程锁,那么另一个线程因为之前已经判断实例没有被实例化,故还会进行再次创建实例,因此在同步代码块中需要再次验证该实例是否已经存在。
- volatile 关键字说明:看到网上有些给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行。实际上,只有很低版本的 Java 才会有这个问题。现在用的高版本的JDK 内部实现中解决了这个问题(解决的方法把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。
- 优点:线程安全,支持懒加载;
- 缺点:实现相对来说复杂一些。
2.3 静态内部类
public class NestedSington {
private NestedSington() {
}
private static class SingtonHolder {
private static NestedSington instance = new NestedSington();
}
public static NestedSington getInstance() {
return SingtonHolder.instance;
}
}
- 优点:线程安全且实现简单支持懒加载;
- 只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。
2.4 枚举单例模式
public enum EnumSington {
INSTANCE;
}
// 需要时使用EnumSington.INSTANCE 即可实现调用。
- 优点:利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。
- 缺点:由于枚举这一特性在java1.5才引入,由于这样的历史原因目前看到的单例模式用的比较少。
- 这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒。
单例的枚举底层原理:【单例深思】枚举实现单例原理
多线程测试各种单例模式代码:
public class SingletonTest extends Thread {
public void run() {
// 延迟加载模式验证
// System.out.println("ThreadId: " + Thread.currentThread().getId() + ",
// Class HashCode: "
// + SingletonLazy.getInstance().hashCode());
// 饿汉模式模式验证
// System.out.println("ThreadId: " + Thread.currentThread().getId() + ",
// Class HashCode: "
// + HungrySingleton.getInstance().hashCode());
// 静态内部类模式验证
// System.out.println("ThreadId: " + Thread.currentThread().getId() + ", Class HashCode: "
// + NestedSington.getInstance().hashCode());
// // 枚举模式验证
System.out.println("ThreadId: " + Thread.currentThread().getId() + ", Class HashCode: "
+ EnumSington.INSTANCE.hashCode());
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new SingletonTest().start();
}
}
}
3. 单例模式在JDK中的应用实例
Returns the runtime object associated with the current Java application.
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
// otherMethon...
4. 单例模式涉及到的知识点
- 为什么说支持懒加载的双重检测并不一定就优于饿汉模式?
其实支持懒加载从一方面来说是一个优点,但也得看情况而定,比如一个对象的加载比较耗时那么每次在运用到时再加载是否会影响到客户的使用体验呢?
还有一些对象加载比较耗空间,那么是否在类加载时就创建会更好呢?因为在类加载时如出现OOM问题反而更容易处理,而不是当用户用到时再加载然后再出现OOM问题。这样也能避免在程序运行一段时间后,突然因为初始化这个实例占用资源过多,导致系统崩溃,影响系统的可用性。
- 为啥面试官如此钟情于单例模式呢?
首先单例模式在常用的23种模式中算是最简单的一种模式,原理以及实现都简单,最主要的是该模式涉及到的基础知识点相对较多,面试官可以从该模式探测到面试者深度与广度。
- 单例模式涉及到的知识点
static :
lock 与 synchronized的区别 :
volatile :
内部类 :
枚举 :
类加载机制:
类的生命周期:
待完善。。。。