单例模式是设计模式中较为重要的一种模式,本文将会从基础分析单例模式设计遇到的一些问题。
Code1
import java.util.*;
class Singleton
{
private static Singleton instance;
private Integer v;
private Boolean flag;
private Singleton()
{
v = new Integer(100);
flag = true;
}
public static Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}
上述代码将构造函数设置为private,这样外界就不可以通过构造函数构造类的实例,要想获得类的实例只有调用静态函数getInstance,保证了获得实例的唯一性。但该段代码仅适用于单线程,在多线程情况下仍有可能两个线程可能同时执行到//2代码处,从而同时获得两个实例。
Code2:
public static synchronized Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
上述代码在将通过synchronized解决getInstabce多线程访问的问题,即使在多线程代码中多个线程调用该函数时获得的都是同一个对象。但是该代码要求每个线程调用时需要阻塞等待另一个线程返回后才能再次调用,降低了程序运行效率。
Code3:
public static Singleton getInstance()
{
if (instance == null) //1
{
synchronized(Singleton.class) { //2
instance = new Singleton(); //3
}
}
return instance;
}
这段代码首先检测instance是否已进行实例化,如果没有进行实例化则在同步代码中进行实例化。但如果仔细分析会发现在多线程环境下仍然无法保证调用该函数获得的对象是同一个。
假如线程1走到//1处,检测instance为null,运行到//2,然后阻塞等待线程2运行,线程2检测instance为null,则也走到//2处,此时线程1运行,通过//3创建instance的实例。线程2等待线程1跳出同步块后也通过//3创建一个实例,这样线程1和线程2获得的instance就不相同了。
Code4:
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
这段代码进行了两次检测,即double-checked locking方法。这段代码解决了Code3中的问题,多个线程获取的实例确为同一个实例,但仔细分析分析代码会发现仍然涉及多线程访问问题。
假如线程1运行到//2,检测到instance为null,则执行//3,条用Singleton的构造函数构造instance,由于构造函数构造实例并非原子操作,当线程1在调用构造函数时阻塞,线程2检测instance不为null,则直接返回instance引用,然后对其进行操作。这时由于instance还未构造成功,因此线程2会得到意想不到的结果,从而造成运行错误。
Code5:
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = null; //2
if (instance == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}
上述代码通过局部变量解决了Code4出现的问题,当Singleton完全初始化后才赋值给instance,这样就不会出现Code4中的问题了。