一、单例模式的好处
可以在整个系统中只使用同一个对象,方便更改整个系统的配置信息,也可以节约系统创建多个相同功能对象的开销。
同时,因为单例模式需要只创建一个对象,所以需要把对象的构造器设置为私有(对象都默认有一个无参构造器)
二、饿汉单例模式
理解: 字面意思,这个模式很饿,所需要的对象,在系统一开始运行就创建出来了(直接使用static实例化对象)。代码实现:
public class HungerSingleton {
//使用static一开始就加载对象
private static HungerSingleton hungerSingleton = new HungerSingleton();
//将默认的无参构造器设置为私有
private HungerSingleton(){
}
//外部调用该方法获取饿汉对象
public HungerSingleton getHungerSingleton(){
return hungerSingleton;
}
}
可以看到很简单就实现了饿汉模式,但是无论是否需要该对象,该对象一开始就必须要加载,这无疑增加了系统的开销,这时,我们可以使用懒汉单例模式解决
三、懒汉单例模式
理解: 懒汉,顾名思义,该单例模式很懒,只有在需要的时候才创建需要的对象。先看实现代码,然后解释。
public class LazySingleton {
//使用volatile防止重排序
private static volatile LazySingleton lazySingleton;
//将默认的无参构造器设置为私有
private LazySingleton(){
}
//双重检查锁
public LazySingleton getLazySingleton() {
if(lazySingleton == null){
synchronized (LazySingleton.class){
if (lazySingleton == null)
return lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
}
疑问:
1. 为什么要使用双重检查锁
版本一:
public LazySingleton getLazySingleton() {
if(lazySingleton == null){
return lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
问题: 在多线程的情况下,线程的抢占式调度,将有可能导致创建多个对象,单例失效,因此需要加上synchronized同步
版本二:
public synchronized LazySingleton getLazySingleton() {
if(lazySingleton == null){
return lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
问题: 我们需要的是在创建对象时进行同步,但是代码中在创建完对象后,之后的每一次获取使用,都需要经过同步操作,在多线程的情况下,大大降低了效率
版本三(双重检查锁):
//双重检查锁
public LazySingleton getLazySingleton() {
if(lazySingleton == null){
synchronized (LazySingleton.class){
if (lazySingleton == null)
return lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
可以看到,我们只是在第一次创建对象的时候进行同步操作,创建完成后的每一次使用,都无需经过同步。
2. 为什么需要使用volatile关键字?
首先看一下对象实例化过程:
1.分配内存空间
2.初始化对象
3.将对象指向刚分配的空间
但是由于编译器等原因的优化,有可能发生重排序顺序可能为:
1.分配内存空间
2.将对象指向刚分配的空间
3.初始化对象
一旦发生了重排序,在多线程的情况下,双重检查锁就会出现问题
所以需要使用volatile关键字,防止发生重排序操作。