单例模式(Singleton)是软件设计中一种比较常见的 , 相对简单的设计模式 .
1 . 单例模式的定义
所谓单例 , 指的就是单示例 , 即某个类的实现对象有且仅能有一个 , 并提供对外调用的方法
2 . 单例模式的特点
- 单例类只能有一个实例
- 单例类必须创建自己的唯一示例 , 其他对象不可替代操作
- 单例类必须向其他对象提供这一实例
3 . 单例模式的实现思路
- 将该类的构造方法设为私有方法 , 外部不可访问 , 仅在类本身中构建类实例
- 提供一个对外的静态方法返回类的实例对象 , 供外部调用
4 . 单例模式的应用场景
- Spring中Bean实例
- 配置文件的读取
- 数据库连接池/多线程线程池的设计
- 统计点击次数
- 统一的任务管理器(如Windows中的任务管理器是无法打开两个的)
5 . 代码实现
1 . 饿汉模式[可用]
/*** @title 单例模式 - 饿汉模式*/
public class Singleton1 {private static Singleton1 instance = new Singleton1();private Singleton1(){};public static Singleton1 getInstance(){return instance;}
}
优点 | 实现简单 , 没有加锁 , 执行效率会提高 , 静态常量也避免了多线程同步的问题 |
---|---|
缺点 | 在类加载时就完成实例化 , 没有延迟加载 , 如果自始至终从未使用这个对象 , 造成内存浪费 |
适用场景 | 占用内存比较小 , 类加载后就会用到的场景 |
2 . 懒汉模式(线程不安全)[不可用]
/*** @title 单例模式 - 懒汉模式(线程不安全)*/
public class Singleton2 {private static Singleton2 instance;private Singleton2(){};public static Singleton2 getInstance(){if(instance == null){instance = new Singleton2();}return instance;}
}
优点 | 实现简单 , 延迟加载 |
---|---|
缺点 | 不支持多线程 , 多线程下如果A线程进入了if(instance == null)判断语句块 , 还未来得及继续向下执行 , B线程也通过了这个判断语句 , 这时便会产生多个实例 |
适用场景 | 单线程情况(实际业务中基本不会只有单线程的场景) |
3 . 懒汉模式-方法锁(线程安全 , 同步方法)[不推荐用]
为了应对第二种的线程安全问题 , 懒汉模式进行了以下改动
/*** @title 单例模式 - 懒汉模式 - 方法锁(线程安全 , 同步方法)*/
public class Singleton3 {private static Singleton3 instance;private Singleton3(){};public static synchronized Singleton3 getInstance(){if(instance == null){instance = new Singleton3();}return instance;}
}
优点 | 实现简单 , 延迟加载 , 线程同步 |
---|---|
缺点 | 效率很低 , synchronized修饰的同步方法比一般方法要慢很多 ,如果频繁调用getInstance , 累积的性能损耗比较大 |
适用场景 | 效率低 , 不推荐用 |
既然synchronized修饰方法效率那 , 那将其放到if的代码块中 , 不就解决了效率低的问题吗 . 对此 , 懒汉模式还有一个变种
if(instance == null){synchronized(Singleton3.class){instance = new Singleton3();}}
这样看起来是解决了问题 , 但是这种方法并不能起到线程同步的作用 . 跟第2种实现方式遇到的情况一致 , 下如果A线程进入了if(instance == null)判断语句块 , 还未来得及继续向下执行 , B线程也通过了这个判断语句 , 这时便会产生多个实例
4 . 懒汉模式-双重锁(线程安全 , 同步代码块)[推荐使用]
综合第三种的2种实现 , 懒汉模式有了以下更安全更效率的实现方式 - 双重锁机制
/*** @title 单例模式 - 懒汉模式-双重锁(线程安全 , 同步代码块)*/
public class Singleton4 {private static Singleton4 instance;private Singleton4(){};public static Singleton4 getInstance(){if(instance == null){synchronized(Singleton3.class){if(instance == null){instance = new Singleton4();}}}return instance;}
}
优点 | 实现简单 , 延迟加载 , 线程同步 , 效率高 |
---|---|
缺点 | 极端情况下双重锁校验会失效 |
适用场景 | 绝大多数场景 |
上述代码看起来可能有些疑惑 , 双重锁机制难道不是需要两个sychronized进行加锁的吗?笔者之前也是这样认为的 , 其实不然 , 这里的双重指的是双重判断 , 而加锁单指那一个sychronized . 为什么要进行双重判断呢 ? 第一重判断 , 如果单例已经存在 , 那么就不需要进行同步操作 , 直接返回当前实例 . 如果没有创建 , 才会进入同步块 , 同步块的目的与之前相同 , 目的是为了防止有两个线程同时调用时 , 导致生成多个实例 , 有了同步块 , 每次只能有一个线程调用能访问同步块内容 , 当创建实例后 , 之后所有的调用都不会进入同步块 , 直接在第一重判断就返回了单例 . 第二重判断 , 是为了防止在极端情况下 , 两个线程都进入了第一个if里面 , 在先后调用同步块时 , 只生成一个实例 .
解决了如上问题 , 是不是表示双重锁模式就万无一失了呢 . 缺点所指的极端情况下双重锁校验失效的问题是什么呢?这里要提一下Java中的指令重排优化 , 所谓指令重排优化就是指在不改变原语义的情况下 ,通过调整指令的执行顺序让程序运行的更快 . JVM中并没有规定编译器优化相关的内容 , 也就是说JVM可以自由的进行指令重排的优化 .
这个问题的关键在于指令重排优化的存在 , 导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的 . 在某个线程创建单例对象时 , 在构造方法被调用之前 , 就为该对象分配了内存空间并将对象的字段设置为默认值 . 此时就可以将分配到的内存地址赋值给instance字段了 , 然后该对象可能还没有初始化 . 若紧接着另外一个线程来调用getInstance , 取到的就是状态不正确的对象 , 程序就会出错 .
以上就是双重锁校验会失效的原因 , 不过好在JDK1.5之后新增加了volatile关键字 . volatile的一个含义就是禁止指令重排优化 , 也就保证了instance变量被赋值的时候对象是已经初始化的 . 从而避免的上面说到的问题 .
private static volatile Singleton4 instance;
但是 , volatile关键字的主要作用是 , 被其所修饰的变量的值不会被本地线程缓存 , 所有对该变量的读写都是直接操作共享内存来实现 , 从而确保多个线程能正确的处理该变量 . 该关键字可能会屏蔽掉虚拟机的一些代码优化 , 所以其运行效率可能有所牺牲 . 5 . 静态内部类[推荐使用]
/*** @title 单例模式 - 静态内部类*/
public class Singleton5 {private Singleton5(){};private static class SingletonInstance{private static final Singleton5 INSTANCE = new Singleton5();}public static Singleton5 getInstance(){return SingletonInstance.INSTANCE;}
}
优点 | 实现难度一般 , 延迟加载 , 线程安全 |
---|---|
缺点 | |
适用场景 | 只适用于静态域的情况? |
6 . 枚举[推荐用]
/*** @title 单例模式 - 枚举*/
public enum Singleton6 {instance;public void method(){}
}
优点 | 实现简单 , 线程安全 |
---|---|
缺点 | 非延迟加载 |
使用场景 | JDK1.5及之后 , 需要序列化的场景 |
这种方式在日常中比较少见 , 但它几乎是单例模式的最佳实现方法 . 它更简洁 , 自动支持序列化机制 , 绝对防止多次序列化 . 它是Effective Java作者Josh Bloch提倡的方式 , 它不仅能避免多线程同步问题 , 而且还能防止反序列化重新创建新的对象 .
6 . 总结
单例模式虽然是众多设计模式中相对简单的 , 但不同的思路也为它带来了丰富的实现 .
在诸多人的总结中 , 它可能有七种/八种 , 但都离不开上述的大致范畴(如懒汉模式的静态方法和静态代码块等) , 这是设计模式的第一文 , 希望大家都能掌握并熟练应用多种设计模式 .