单例模式简介
定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
-
UML类图:
-
综合分析:
优点:
- 对于频繁创建销毁的对象可减少内存开支。
- 对于配置复杂、依赖较多的对象,可减少性能开销。
- 避免多个兄弟对象之间争夺资源。
- 便于设置全局访问点。
缺点:
- 单例模式拓展困难。
- 对于测试是困难的。
- 与单一指责原则冲突。
适用场景:
- 系统唯一对象,例如序列号生成器。
- 系统共享点和共享数据,例如程序计数器、映射表等。
- 创建一个耗费大量资源的对象,例如数据库对象。
- 大量静态方法,例如工具类。
单例模式实践
- 标准模式
- 懒汉模式
- 饿汉模式
- 双重锁懒汉模式
- 静态内部类
- 枚举模式
1. 标准模式
//标准单例模式
public class StandardSigleton {
private static StandardSigleton singleton;
private StandardSigleton() {}
public static StandardSigleton getInstance() {
if (singleton == null) {
singleton = new StandardSigleton();
}
return singleton;
}
}
- 特点:以时间换空间,具有更好的用户体验,线程不安全。
2. 懒汉模式
//懒汉单例(线程安全)
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton(){}
public static synchronized LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
}
- 特点:线程安全,synchronized的开销影响性能。
3. 饿汉模式
//饿汉单例模式(线程安全)
public class HungrySingleton {
private static HungrySingleton singleton =
new HungrySingleton();
private HungrySingleton() {}
public static HungrySingleton geInstance() {
return singleton;
}
}
- 特点:空间换时间,线程安全(推荐使用)。
4. 双重锁懒汉模式
//双重锁懒汉模式(Double Check Lock)
public class DCLSingleton {
private volatile static DCLSingleton singleton = null;
private DCLSingleton() {};
public static DCLSingleton getInstance() {
if (singleton == null) {
synchronized (DCLSingleton.class) {
if (singleton == null) {
singleton = new DCLSingleton();
}
}
}
return singleton;
}
}
增加volatile的原因:
singleton = new DCLSingleton()的时候会进行三个步骤:
- 堆内分配空间。
- 在堆在初始化参数等信息。
- 对象指针指向堆内存地址。
由于JVM存在乱序执行,在多线程中,会出现一种情况,由于指令重排序的原因,线程A的第三步(指向内存地址)的执行优先于第二步,此刻线程B却以为对象已经初始完,线程B使用该对象便会出错。而volatile保证JVM不出现指令重排序。
5. 静态内部类模式
//静态内部类单例模式
public class InnerClassSingleton {
private InnerClassSingleton() {}
private static class SingletonHoler {
private static final InnerClassSingleton INSTANCE =
new InnerClassSingleton();
}
public static InnerClassSingleton getInstance() {
return SingletonHoler.INSTANCE;
}
}
- 特点:高效,线程安全,但存在传参问题。
高效原因:第一次加载InnerClassSingleton时不需要分配InnerClassSingleton的内存空间(懒加载优势),而调用getInstance()时,SingleTonHoler才在InnerClassSingleton的运行时常量池里,把符号引用替换为直接引用,这时静态对象INSTANCE也真正被创建。
线程安全:JVM会保证一个类的<clinit>()方法在多线程环境下正确的加锁、同步,从而保证在<clinit>()方法执行时,只有一个线程去执行,阻塞其他线程,达到线程安全的目的。
注:此处<clinit>()可以理解为对static对象的初始化赋值。
6. 枚举模式
public enum SingleTon{
INSTANCE;
public void method(){
//TODO
}
}