顾名思义,单例模式(Singleton Pattern)就是保证一个类有且仅有一个实例,并且提供了一个全局的访问点。这就要求我们绕过常规的构造器,提供一种机制来保证一个类只有一个实例,客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任。
单例类的特点总结如下:
1. 只能有一个实例
2. 构造方法应该由private修饰,也就是自己创建自己的唯一实例
3. 必须给其他对象提供这一实例
上文已经提到,单例类要实现有且仅有一个实例必须要设计者对该类进行处理,下面就简单介绍一下如何设计一个单例类。
1. 饿汉模式
public class MySingleton {
//自创的单例
private static final MySingleton instance = new MySingleton();
//私有的单例构造方法,避免外部创建
private MySingleton() {
}
//公有的静态方法,为外部提供唯一的单例类
public static MySingleton getInstance(){
return instance;
}
}
从上面代码可以看出,饿汉模式在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。通过用private修饰构造方法,保证了外部不能对该单例类进行实例化。
2. 懒汉模式
public class MySingleton {
private static MySingleton instance = null;
// 私有构造
private MySingleton() {}
// 静态工厂方法
public static MySingleton getInstance() {
if(instance == null){
instance = new MySingleton();
}
return instance;
}
}
上面就是懒汉式单例的简单写法,可以看到在多线程情况下会产生线程安全问题。假设MySingleton类刚刚被初始化,instance为空,此时两个线程同时访问getInstance方法,因为instance为空,所以两个线程同时通过了条件判断,都new出了一个MySingleton实例,MySingleton类被构建了两次。为了解决这个问题,可以使用双重检测机制进行约束,代码如下:
public class MySingleton {
private static MySingleton instance = null;
// 私有构造
private MySingleton() {
}
//静态工厂方法
public static MySingleton getInstance() {
if (instance == null) {//双重检测
// 等同于 synchronized public static MySingleton getInstance()
synchronized (MySingleton.class) {//同步代码块
// 注意:里面的判断是一定要加的,否则出现线程安全问题
if (instance == null) {//双重检测
instance = new MySingleton();
}
}
}
return instance;
}
}
上面代码为了防止线程安全问题,用synchronized同步锁对相关代码块进行了同步处理。如下图,假设线程A和线程B同时做完判空处理且线程A首先获得锁,执行完代码块并new了一个MySingleton实例后释放锁,此时线程B获取同步锁来到第二条判空语句时,此时instance不为空,返回线程A创建好的单例实例。
上面代码看似没任何问题,但当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法,如下图所示。此时线程A要么已经构建完instance(线程B判断时返回false),要么还未构建完instance(线程B判断时返回true),但事实并非如此,这里涉及到JVM编译器的指令重排,也就是java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:
memory =allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance =memory; //3:设置instance指向刚分配的内存地址
这些指令的顺序并非一成不变,经过JVM和CPU的优化可能会改变顺序。这时会出现线程B获取到一个未初始化完成的instance对象,在instance对象前面增加一个修饰符volatile可以避免发生这种情况,volatile关键字保证了instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。
线程安全的单例类写法如下:
public class MySingleton {
private volatile static MySingleton instance = null;
// 私有构造
private MySingleton() {
}
//静态工厂方法
public static MySingleton getInstance() {
if (instance == null) {//双重检测
// 等同于 synchronized public static MySingleton getInstance()
synchronized (MySingleton.class) {//同步代码块
// 注意:里面的判断是一定要加的,否则出现线程安全问题
if (instance == null) {//双重检测
instance = new MySingleton();
}
}
}
return instance;
}
}
3. 静态内部类实现
public class MySingleton {
//内部类
private static class InnerObject {
private static final Singleton instance = new MySingleton();
}
//私有的单例构造方法,避免外部创建
private MySingleton() {
}
//公有的静态方法,为外部提供唯一的单例类
public static MySingleton getInstance(){
return InnerObject.instance;
}
}
上面方法需要注意的是instance对象并不是在单例类Singleton被加载的时候初始化的,而是在调用getInstance方法是通过静态内部类加载。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。
4. static静态代码块实现
public class MySingleton {
//自创的单例
private static final MySingleton instance = null;
//私有的单例构造方法,避免外部创建
private MySingleton() {
}
static {
instance = new MySingleton();
}
//公有的静态方法,为外部提供唯一的单例类
public static MySingleton getInstance(){
return instance;
}
}
5. 枚举类实现
上述所说的单例类实现方式均可通过反射来访问单例类中的私有构造方法,从而构造出多个单例对象,这和单例对象的定义违背,我们可以通过设计内部枚举类来防止该类问题。枚举类单例不是懒加载,其单例对象是在枚举类被加载时初始化的。
public enum MySingletonEnum {
instance;
}
单例模式应用的场景一般发现在以下条件下:
根据上诉条件列出以下常用的应用场景:
好了,以上就是单例类的几种常见实现方式和用用场景,参考文章
https://zhuanlan.zhihu.com/p/33102022
https://www.cnblogs.com/restartyang/articles/7770856.html