是什么?
单例模式是Java中最简单的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式;
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象;
结构
单例模式主要有以下角色:
1.单例类:只能创建一个实例的类;
2.访问类:使用单例类;
实现
饿汉式
类加载就会导致该单实例对象被创建;
代码实现
我们在类加载的时候就会实例化出该对象存放在内存中,不需要在使用的时候再去创建,因此不会有多个Singleton对象实例存在,当类被卸载时,该对象也会随之消亡;
public class HungrySingleton {
/**
* 模拟饿汉式创建单例对象
* */
private final static HungrySingleton hungrySingleton=new HungrySingleton();
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
懒汉式
类加载不会导致该单实例对象被创建,而是首次使用该对象的时候才会创建;
代码实现
我们直接来看最终的代码,我们不仅要在使用的时候进行单例对象的实例化,还需要防止在并发的情况下重复创建;
因此我们最终使用的方案是双重判空+锁的方式来处理,这样处理的好处就在于:如果在并发的情况下,首先回去进行非空判断,不至于让所有线程一上来就直接竞争锁资源,提高了性能,其次又为了防止在加到锁之后其他线程已经完成了实例化,因此再进行判断,这样的话更加安全和保险;
/**
* 模拟懒汉式创建单例对象
* */
private static volatile Singleton singleton; //防止指令重排
public static Singleton getInstance() {
if (singleton==null){
synchronized (Singleton.class){ //加锁防止并发的情况下重复实例化对象
if (singleton!=null){ //双重判空
return singleton;
}else {
singleton=new Singleton();
}
}
}
return singleton;
}
对单例对象加上volatile关键字是为了防止指令重排序,因为JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能,但是这样的话就有可能报空指针异常,因此我们使用volatile修饰,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变化,其次使用volatile关键字修饰的变量可以保证其内存可见性,即每一时刻读取到该变量的值都是内存中最新的那个纸,线程每次操作该变量都需要读取该变量;
如下图,如果线程A发生了指令重排,没有在第二步的时候进行对象的初始化,而这个时候线程B刚好在这个时间差去使用该对象就会报NPE异常;