定义与介绍
指一个类只有一个实例,且该类能自行创建这个实例的一种模式。
使用场景: 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
优点:
-
在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 -
避免对资源的多重占用(比如写文件操作)。
缺点:
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,扩展困难。
单理模式的实现
1. 懒汉式,线程不安全
使用了懒加载的设计,即只在需要调用是才进行初始化,但是不能保证线程并发安全。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2. 懒汉式,线程安全
为了解决线程安全问题,直接对整个方法加锁。
但只会在第一次调用时进行初始化,这要书写会导致后续每次都只能有一个线程进行调用,效率不高。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3. 懒汉式,双重校验锁
不对整个方法上锁,只在进行初始化时,对代码块进行上锁。
问:为什么有两次校验?
答:可能有多个线程同时进入第一个if 校验,防止重复初始化。
问:为什么加 volatile ?
答:因为 instance = new Singleton(); 这句代码并不是原子性操作,在 JVM 中大概有以下操作:
- 给 instance 分配内存。
- 调用 Singleton 的构造函数来初始化成员变量。
- 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)。
但是 JVM 并不一定按照 1-2-3 来的,也可能是 1-3-2 。
如果是第二种,当执行到 3 时,下一个线程进来,检验到的 instance 不为空,则返回进行调用,就会出现报错的情况。
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4. 饿汉式
类一旦加载就创建一个单例,没有加锁,执行效率提高,但是缺点也很明显,如果这个类没有被调用会浪费一定的内存空间。
且有些类是需要根据配置文件加载,或根据不同参数进行设置的,在这种情况下饿汉式写法就无法满足需求。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
5. 静态嵌套类
这种写法的原理是:当 JVM 加载一个类时,不会去加载这个类的嵌套类。
只有当我们去调用 getInstance() 时, JVM 才会去初始化 SingletonHolder 。
静态属性保证了全局唯一,静态变量初始化保证了线程安全,所以这里的方法没有加 synchronized 关键字( JVM 保证了一个类的 初始化在多线程下被同步加锁 )。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6. 枚举
创建枚举默认就是线程安全的,所以不需要加锁。这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
public enum Singleton{
INSTANCE;
}
总结
一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy loading )会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。如果有其他特殊的需求,可以考虑使用双重校验锁的方式。
|