1. 定义
Ensure a class has only one instance, and provide a global point of access to it.
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
2. 要点
- 只创建一次
- 通常将构造器私有化
- 需注意线程安全
- 懒加载(lazy loading)
- 一般不考虑反射(Java反射可以实例化private)
3. 单例模式的种类
3.1 饿汉模式
- 3.1.1 公有的静态final域
1 2 3 4 5 6 7 8
| public class Singleton_Hungry extends BasePattern {
public static final Singleton_Hungry singleton = new Singleton_Hungry();
private Singleton_Hungry() { }
}
|
分析
- 够简单粗暴
- 代码清晰表明是个单例
- 无lazy loading
1 2 3 4 5 6 7 8 9 10 11
| public class Singleton_Hungry extends BasePattern {
private static final Singleton_Hungry singleton = new Singleton_Hungry();
private Singleton_Hungry() { }
public static Singleton_Hungry getInstance() { return singleton; } }
|
分析:
- 线程安全(使用ClassLoader机制避免了多线程同步)
- 没有实现懒加载(lazy loading),将类的创建委托给了类装载器
- 主要在类加载时,就自行初始化了
- 也可以在static块中进行初始化
- 静态工厂实现,有更大灵活性
3.2 懒汉模式
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton_Lazy extends BasePattern {
private static Singleton_Lazy singleton = null;
private Singleton_Lazy() { }
public static Singleton_Lazy getInstance() { if (singleton == null) singleton = new Singleton_Lazy(); return singleton; }
}
|
分析:
- Lazy Loading
- 并发下,线程不安全,有可能重复创建对象
- 不推荐在生产中使用
3.3 改进式饿汉模式
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton_Lazy_Sync {
private static Singleton_Lazy_Sync singleton = null;
private Singleton_Lazy_Sync() { }
public static synchronized Singleton_Lazy_Sync getInstance() { if (singleton == null) singleton = new Singleton_Lazy_Sync(); return singleton; } }
|
备注: synchronized 确保只有一个线程访问
分析
- 保证了getInstance()的并发情况下的,只能有一个线程访问
- 效率过低,每次都要确保同步的情况下获取实例
- 不推荐使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton_Lazy_Sync {
private static Singleton_Lazy_Sync singleton = null;
private Singleton_Lazy_Sync() { }
public static Singleton_Lazy_Sync getInstance() { synchronized(Singleton_Lazy_Sync.class){ if (singleton == null) singleton = new Singleton_Lazy_Sync(); } return singleton; } }
|
分析
- 加synchronized确保并发情况下能其为同一时刻只能有一个线程能够访问
- 对Singleton_Lazy_Sync类加同步关键字,效率同样偏低
3.4 双重校验锁方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Singleton_DoubleCheck {
private volatile static Singleton_DoubleCheck singleton;
private Singleton_DoubleCheck() { }
public static Singleton_DoubleCheck getInstance() { if (singleton == null) { synchronized (Singleton_DoubleCheck.class) { if (singleton == null) { singleton = new Singleton_DoubleCheck(); } } } return singleton; } }
|
备注:Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
分析
- 单例仅new一次,在synchronized 加上判空语句,仅执行一次同步锁定,其余的直接跳过
- 是基本懒汉式的改进型
- volatile 确保变量值直接从内存读取(多个线程中存在副本),另,该特性java 1.5版本之后才有用
- volatile 会禁止指令重新排序
- 实现有点复杂
3.5 静态内部类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton_InnerClass {
private static class SingletonHolder { private static Singleton_InnerClass singleton = new Singleton_InnerClass(); }
private Singleton_InnerClass() { }
private static final Singleton_InnerClass getInstance() { return SingletonHolder.singleton; } }
|
分析
- 同样利用了classloder的机制来保证初始化instance时只有一个线程
- 静态内部类只会加一次,故线程安全
- 外部类能直接访问内部类(不管是否是静态的)的私有变量
- 内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候仅实例化一次instance。
3.6 枚举
1 2 3
| public enum Singleton { INSTANCE; }
|
分析
- 简洁优雅
- 无偿提供了序列化机制,绝对防止多次实例化,即使使用复杂的序列化或反射攻击
- 单元素的枚举类型
- 枚举实例的创建默认就是线程安全
4 单例模式扩展
4.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 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class Singleton_FixedNum {
private static int MAX_NUM = 30;
private static List<Singleton_FixedNum> list = new ArrayList<>();
private static Random random = new Random();
static { for (int i = 0; i < MAX_NUM; i++) { list.add(new Singleton_FixedNum(i)); } }
private int num;
public int getNum() { return num; }
public void setNum(int num) { this.num = num; }
private Singleton_FixedNum() { }
private Singleton_FixedNum(int num) { this.num = num; }
public static Singleton_FixedNum getInstance() { return list.get(random.nextInt(MAX_NUM)); }
}
|
分析
- 单例的一种扩展,限定产生的实例的数量
- 也可以创建动态数量的实例,如线程池
4.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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class Singleton_Reg {
private static HashMap m_reg = new HashMap<>();
static { Singleton_Reg singleton_reg = new Singleton_Reg(); m_reg.put(singleton_reg.getClass().getName(), singleton_reg); }
protected Singleton_Reg() { }
public static Singleton_Reg getInstance(String name) { if (name == null) { name = Singleton_Reg.class.getName(); } if (m_reg.get(name) == null) { try { m_reg.put(name, (Singleton_Reg) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return m_reg.get(name); }
}
public class Singleton_RegChild extends Singleton_Reg {
public Singleton_RegChild() { }
public static Singleton_RegChild getInstance() { return (Singleton_RegChild) Singleton_Reg.getInstance(Singleton_RegChild.class.getName()); }
public static void main(String[] args) { Singleton_RegChild.getInstance(); } }
|
分析
- 登记式单例类是Gof为了克服饿汉式和懒汉式单例均不可继承而设计的
- 子类实现只能是懒汉式
- 缺点: (1)由于子类必须允许父类以构造器调用产生实例,因此,
它的构造方法必须是公开的这样一来,就等于允许了以这样方式产生实例
而不在父类的登 记中。(2)由于父类的实例必须存在才可能有子类的实例,
这在有些情况下是一个浪费。这是登记式单例类的另一个缺点
5. 优点
- 提供唯一个实例的受控访问,减少内存开支,减少系统性能的开销
- 避免对资源的多重占用
- 可以设置全局访问点,优化和共享资源访问
- 允许可变数目的实例
6. 缺点
- 单例模式没有接口,难以扩展
- 单例类的职责过重,一定程度上违背了“单一职责原则”
7. 应用场景
- 项目中需要一个共享访问点或共享数据
- 创建一个对象需要消耗的资源过多,如要访问IO
- 需要定义大量的静态常量和静态方法(也可以直接使用static)
8. 引用
(秦小波)《设计模式之禅》第7章
(Joshua Bloch)《Effective Java》 第2章 第3条