单例模式
针对要求软件系统中只保留某一个类的一个实例的业务场景。我们常写的普通的软件类,在高并发场景下,会创建多个实例,容易产生线程同步问题。因此需要采用特殊的设计模式完成类的设计。
如 Hibernate 中的 sessionfactory 就要求整个应用中只存在一个实例。此外还有一些应用场景,如日志、线程池、数据源、硬件设备驱动等。
1. 饿汉式-静态常量方式
java
public class Singleton{
// 1. 构造器私有化,防止外部new
private Singleton(){}
// 2. 本类内部创建实例对象
private final static Singleton instance = new Singleton();
// 3. 对外暴露使用方法
public static Singleton getInstance(){
return instance;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 在类加载时完成实例化,避免了线程同步的问题;
- 由于类加载机制中类的加载有很多中方式,因此不能确定是否有其他方式导致类加载;
- 如果软件过程中没有用到,可能会造成内存浪费;
- 可在生产环境下使用;
- 使用场景
- java.lang.Runtime 使用的就是这种单例模式的实现方式;
2. 饿汉式-静态代码块方式
java
public class Singleton{
// 1. 构造器私有化,防止外部new
private Singleton(){}
// 2. 声明
private static Singleton instance;
// 3. 静态代码块中进行实例化
static {
instance = new Singleton();
}
// 4. 对外暴露使用方法
public static Singleton getInstance(){
return instance;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 与上面方式类似,只不过是把类实例化过程放到了静态代码块中;
- 也可以在生产环境下使用,并且优缺点与上面方式一样;
3. 懒汉式(线程不安全)-无同步措施方式
java
public class Singleton{
// 1. 构造器私有化,防止外部new
private Singleton(){}
// 2. 声明
private static Singleton instance;
// 3. 对外暴露使用方法,如果对象不存在就生成一个进行返回
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 使用了懒加载的方式,但是只能在单线程模式下使用;
- 如果一个线程执行到了判断语句,而另一个线程也执行到了判断语句,这时就会产生两个实例;
- 不建议在生产环境下使用;
4. 懒汉式(线程安全)-同步方法
java
public class Singleton{
// 1. 构造器私有化,防止外部new
private Singleton(){}
// 2. 声明
private static Singleton instance;
// 3. 对外暴露使用方法,如果对象不存在就生成一个进行返回,并且生成方法属于同步方法,避免线程同步问题;
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 在上一种模式的基础上,对使用方法进行优化,添加了 synchronized 关键字;
虽然这种方式是线程安全的,但这种方式的效率太低了
,每一个想要获取实例的方法都需要进行同步进行等待,并且 get 方法实际上只需要执行一次即可,后面的使用可以直接返回即可。- 生产环境下不推荐使用这种方式;
5. 懒汉式(线程不安全)-同步代码块
java
public class Singleton{
// 1. 构造器私有化,防止外部new
private Singleton(){}
// 2. 声明
private static Singleton instance;
// 3. 对外暴露使用方法,如果对象不存在就生成一个进行返回,并且生成方法属于同步方法,避免线程同步问题;
public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 虽然使用了同步代码块的方式,但是如果多线程执行 判断条件 时,可能会同步创建出多个实例
- 生产环境下依然不建议使用;
6. 双重锁校验(DCL)
java
public class Singleton{
// 1. 构造器私有化,防止外部new
private Singleton(){}
// 2. 使用 volatile 关键字来声明
private static volatile Singleton instance;
// 3. 对外暴露使用方法,如果对象不存在就生成一个进行返回,并且生成方法属于同步方法,避免线程同步问题;
public static Singleton getInstance(){
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 第一次校验时,如果之前已经完成实例化,就直接返回,不用再次生成,提高效率;
- 如果没有第二次校验,在并发情况下,线程 a 和线程 b 同时进入同步代码块,假设线程 a 先执行同步代码块进行,完成实例化,之后线程 b 获取执行权限后会再次生成一个实例;加上第二次校验后,线程 b 获取执行权限后判断已经完成实例化,就会直接跳出判断返回实例化对象;
- 高并发场景下,线程 a 和线程 b 同时执行到 synchronized 处获取的 instance 实例为 null,但是线程 a 完成实例化后,等待执行权限的线程 b 能够通过 volatile 关键字立刻获取到 instance 变量的通知,随后 instance 变成非空;
- 延迟加载,效率较高,建议在生产环境下使用;
7. 静态内部类
java
public class Singleton{
private Singleton(){}
private static class SingletonHolder{
private final static Singleton INSTANCE = new Singleton();
}
public static getInstance(){
return SingletonHolder.INSTANCE;
}
}
// 使用
Singleton oneSingleton = Singleton.getInstance();
- 分析
- 静态内部类 SingletonHolder 在类初始化时,并不会立即加载内部类,内部类不会加载,就不会生成 INSTANCE 实例,就不会占用内存空间;只有当 getInstance()方法第一次被调用时,才会加载内部类,完成初始化;
- jvm 保证同一个加载器类型下,一个类型只会初始化一次;
- 由于是静态内部类方式完成的实例化,因此没有办法完成参数的传递;
- 这种方法不仅能够保证线程安全、也保证了单例的唯一性,同时还延迟了单例的实例化;
- 推荐使用
8. 枚举类
略。
- 分析
- 能避免多线程同步问题,还能避免反序列化时生成多个实例的问题;
- 推荐使用
9. 单例模式在 jdk 中的使用
java.lang.Runtime 就是经典的饿汉式单例模式;