饿汉模式
为啥叫饿汉估计也跟初始化有关,有点迫不及待的意思,因为在声明静态对象的时候就已经初始化了。并且此方法线程安全。
public class Singleton{
private static final Singleton mInstance = new Singleton();
public static Singleton getSingleton(){
return mInstance;
}
}
懒汉模式
与迫不及待相对的,有种比较懒的方式,在你获取的时候我才初始化。当然这一点还不足以体现它的“懒”,为了方法能够线程安全,进行了同步,但是大家有没有发现只要调用这个方法就会进行同步(synchronized),而同步是非常消耗资源的。
public class Singleton{
private static final Singleton mInstance = null;
public static synchronized Singleton getSingleton(){
if(mInstance==null){
mInstance = new Singleton();
}
return mInstance;
}
}
Double Check Lock(DCL)实现单例
既然前面两种都有缺陷那么有没有既不饥渴难耐又不懒且线程安全的实现模式呢?答案是有的,进行双重检查,第一层判断是为了避免不必要的同步,第二层是为了担心在多线程中第一个进来的时候已经创建了,所以再判断一次。
public class Singleton{
private static final Singleton mInstance = null;
public static Singleton getSingleton(){
if(mInstance==null){ //第一重检查
synchronized (Singleton.class){
if(mInstance==null){ //第二重
mInstance = new Singleton();
}
}
}
return mInstance;
}
}
乍一看完美!这就是我想要的,可是真的没有缺陷了?
让我们来看一个问题,当执行mInstance = new Singleton()的时候看起来是一句代码执行,但是在虚拟机里面不是这样的,他大概在虚拟机里执行了三件事
- 给Singleton的实例分配内存(此时还没有指向mInstance)
- 调用构造函数,初始化成员
- 将mInstance对象指向分配的内存空间
在jdk1.5之前java内存模型中cache和寄存器到主内存的回写顺序规定,还有java编译器允许乱序执行,所以执行顺序可能不一致有的是1-2-3有的是1-3-2,在多线程中很可能线程A做了1步和第3步还没来得及做第二步的时候,就被切换到B线程,B线程发现第三步已经执行了,所以直接拿来用了,但是这个时候是错误的,因为第二步没有执行,成员未被初始化,这就是DCL失效,jdk1.5以后调整了jvm,因此如果JDK是1.5或之后的版本,只需要将instance的定义改成“private volatile static Singleton mInstance = null;”就可以保证每次取instance都从主内存读取,就可以使用DCL的写法来完成单例模式
另外基于此我们还可以优化一下,因为volatile也是一个比较重的操作(volatile解析),在 mInstance不为空的时候(这是绝大部分的情况),只要在开始访问一次 volatile 变量,返回的是临时变量。如果没有此临时变量,则需要访问两次,而降低了效率
public class Singleton{
private volatile static final Singleton mInstance = null;
public static Singleton getSingleton(){
//开始访问一次volatile 变量
Singleton Instance=mInstance;
if(mInstance==null){ //第一重检查
Instance=mInstance;
synchronized (Singleton.class){
if(Instance==null){ //第二重
mInstance= new Singleton();
Instance=mInstance;
}
}
}
//最后返回局部变量,这样少访问了一次
return Instance;
}
}
静态内部类单例模式
那有没有一种继承DCL优点,但是没有其缺点的方式呢,答案就是静态内部类单例模式,这种方法使用内部类来做到延迟加载对象,在初始化这个内部类的时候,JLS(Java Language Sepcification)会保证这个类的线程安全。这种写法最大的美在于,完全使用了Java虚拟机的机制进行同步保证,没有一个同步的关键字。
public class Singleton{
public static Singleton getInstance(){
return SingletonHolder.mInstance;
}
private static class SingletonHolder{
private static final Singleton mInstance = new Singleton();
}
}
知识扩展,让我们简单了解下其他实现方式:
- 枚举单例(枚举默认线程安全,并且任何情况下都是一个单例 哈哈哈 是不是很完美)
- 使用容器实现单例模式(把许多单例注入到一个容器中,然后根据key取)