需求
创建一个全局唯一的对象,在对象创建并初始化完成之前不允许任何其它线程获取该对象的引用。在对象创建并初始化完成之后不允许创建新的对象代替该对象。
解决思路
在类方法中提供一个getSingleton()
方法,保证此方法的所有调用中始终返回同一个对象。
UML类图
实现方式
懒汉模式
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
这个模式不是线程安全的模式,在访问时才创建,效率较高。
饿汉模式
public static Singleton instance = new Singleton();
这种模式是线程安全的,但是失去了延迟初始化资源带来的好处。
线程安全的饿汉模式
volatile 和双检查锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton(){
}
private Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
上面的代码之所以使用双重检查锁的同时还是用volatile是因为singleton = new Singleton()
这段代码不是一个原子操作,并且可能发生重排序:
上面的代码包括下面三个操作:
- 给singleton指向的对象分配内存空间
- 在该内存上构建Singleton类型的实例
- 将这片内存的地址赋值给singleton引用,这个操作完成之后singleton不是null
以上的第二个操作和第三个操作没有依赖关系,也就是无论哪个在前都不会影响程序的正常执行。可是使用双重检查锁的代码以第三个操作是否完成作为判断singleton是否完成初始化的标准,这是不准确的。所以需要使用volatile关键字禁止重排序。
使用静态内部类
public class Singleton {
private Singleton(){
}
public Singleton getInstance(){
return SingletonHolder.SINGLETON;
}
static class SingletonHolder{
private static final Singleton SINGLETON = new Singleton();
}
}
这种模式是利用了jvm 类加载的同步控制机制。当一个类第一次被使用的时候,这个类被加载。JVM使用了同步控制机制保证每个类只被加载一次,具体的方法是负责加载该类的线程初始化类变量的时候获取该类的Class对象的锁,使得初始化过程是一个原子的操作,即使重排序了,也没有影响,因为没有那个错误的判断操作了。