1.单例模式确保某各类只有一个实例,必须自己创建自己的唯一实例。一般通过将构造方法声明为private避免类在外部被实例化,在同一虚拟机范围内,Singleton的唯一实例只能通过getInstance()访问。
2.实现方式:
方案一:懒汉式单例类,在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton singleton = null;
//静态工厂方法
public static Singleton getInstance() {
if (singleton == null) { //1
singleton = new Singleton(); //2
}
return singleton; //3
}
}
分析:上面的懒汉式没有考虑线程安全问题,在并发环境下很可能会出现多个Singleton实例。
上面的程序只适用于单线程程序,当有2个线程调用getInstance方法时:
1. 线程1调用getInstance()方法,并判断singleton在//1处为null;
2. 线程1进入if代码块,但还未执行//2处;
3. 线程2调用getInstance()方法,并判断singleton在//1处为null;
4. 线程2进入if代码块,并创建一个新的Singleton对象并在//2处将变量singleton分配给这个新对象;
5. 线程2在//3处返回singleton对象;
6. 线程1在它停止的地方启动,并执行//2代码,这导致创建另一个Singleton对象;
7. 线程1在//3处返回singleton对象;
结果就是getInstance()方法创建了两个Singleton对象。
方案二.在getInstance()方法上加同步
public static synchronized Singleton getInstance() {
if (singleton == null) { //1
singleton = new Singleton(); //2
}
return singleton; //3
}
存在的问题:
由于只有第一次调用执行了//2处的代码,而只有此行代码需要同步;
因此就无需对后续调用使用同步,所有其他调用判断singleton是非null,然后将其返回
由于方法是synchrinized的,需要为方法的每一步调用付出同步的代价。
方案三.双重检查锁定:
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) { //1
if (singleton == null) { //2
singleton = new Singleton(); //3
}
}
}
return singleton;
}
当singleton为null时,两个线程可以并发地进入if语句内部,然后一个线程进入synchronized块来初始化singleton,而另一个线程则被阻断。当第一个线程退出synchronized块时,等待着的线程进入并创建另一个Singleton对象,但是当第二个线程进入synchronized块时,它必须检查singleton是否为null。
存在的问题:(JMM的指令重排序)
在代码//3处,创建一个Singleton对象并初始化变量singleton来引用该对象
这行代码出现的问题是:在Singleton构造函数体执行之前,变量singleton可能成为非null
1.线程1进入getInstance()方法
2.由于singleton为null,线程1在//1处进入synchronized块
3.线程1进入到//3处,但是在构造函数执行之前,实例singleton非null
4.线程1被线程2预占
5.线程2检查singleton是否为null,因为singleton不为null,线程2将singleton引用返回
给一个构造完整但部分初始化了的Singleton对象
6.线程2被线程1预占
7.线程1通过运行Singleton对象的构造函数并将引用返回给它,完成对象的初始化
上述过程发生了线程2返回一个尚未执行构造函数的对象。
代码:singleton = new Singleton();
1.mem = allocate(); //Allocate memory for Singleton object.
2.instance = mem; //Note that instance is now non-null, but has not been initialized.
3.ctorSingleton(instance); //Invoke constructor for Singleton passing instance.
方案四.静态内部类(建议使用)
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private final static Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。
而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,
由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
达到了lazy loading的效果,即按需创建实例。
方案五.饿汉式单例(建议使用)
public class Singleton {
private Singleton() {}
private static Singleton singleton = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return singleton;
}
}
饿汉式和懒汉式区别
1.饿汉式是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例已经存在
懒汉式只有当调用getInstance方法时,才初始化这个类;
2.线程安全
饿汉式天生就是线程安全的,可直接用于多线程而不会出现问题
懒汉式非线程安全的
3.资源加载和性能
饿汉式在类创建的同时就实例化一个静态对象,不管之后会不会使用这个单列,都会占用一定的内存,
但是相应的在第一次调用的时候速度更快,因为其资源已经初始化完成;
懒汉式会延迟加载,在第一次使用该单例的时候才会实例化该对象;
方案二在方法调用上加了同步,虽然线程安全,但是每次都要同步,影响性能
方案三在getInstance中做了两次null检查,但是会由于JMM的指令重排序,可能会出现线程不安全情况;