今天在看了设计模式解析之后对singleton又有了新的认识, 在此记一下哈
一个最简单的Singleton实现:
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
考虑了并发的Singleton实现:
public class DoubleCheckedSingleton {
private static DoubleCheckedSingleton instance;
private DoubleCheckedSingleton(){}
//方法级别的synchronized会成为性能瓶颈
public static DoubleCheckedSingleton getInstance(){
//可以将synchronized关键字放在这里,但是调用者每次getInstance时都会
//在==null的判断前等待,造成性能瓶颈.可以在==null外面再套一层,这样只会
//在实例化之前阻塞程序
if(instance == null){
synchronized(DoubleCheckedSingleton.class){
if(instance == null){
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
这样的代码看上去似乎没什么问题, 但是存在指令重排, Object o = new Object 可分解为三个操作:1. 申请一片内存空间, 2.调用Object的构造方法, 3.将new的对象的引用赋值给o.编译器出于优化程序的目的, 会适当调整123的顺序, 比如会变成132. 在3和2之间,此时假设另一个线程进入了getInstance方法,判断得到的instance不为null, 于是乎就把这个引用拿走使用了, 但是其实这个引用并不是一个有效的引用, 因为他还没构造完成. 因此这样的方式还是无法解决同步的问题. jdk1.5之后, 加强了volatile关键字的语义, 使用volatile关键字就可以禁止指令重排问题了, 即吧instance声明为volatile.
volatile的两个语义:一是对volatile变量的写操作happen-before对同一变量的读操作, 二是屏蔽指令重排序.
不过书中提出了一只利用java类加载器的机制实现了只初始化一次
public class InnerClassSingleton {
private InnerClassSingleton(){}
private static class Instance{
static final InnerClassSingleton instance = new InnerClassSingleton();
}
public static InnerClassSingleton getInstance(){
return Instance.instance;
}
}
即Instance类只会被加载一次(member class的加载时机为使用时加载,见http://stackoverflow.com/questions/24538509/does-the-java-classloader-load-inner-classes),所以只会创建一个对象.JVM能够保证当一个类被加载的时候, 这个类的加载过程是线程互斥的。因此当我们在第一次调用getInstance方法的时候,JVM能够确保instance只被创建一次。
public class MySingleton {
private static MySingleton instance = new MySingleton();
private MySingleton(){}
public static MySingleton getInstance(){
return instance;
}
}
作为静态成员变量的instance应该照样只会加载一次吧, 相比于第一种Singleton的实现, 无非就多了一块内存的申请罢了,
---------------------华丽的分割线-------------------
上述程序确实可以很好的实现并发中的单例, 但是在第一次加载类的时候就创建了MySingleton,并不适用于惰性加载的情况。而使用内部类实现的话,由于在jvm中“当一个类被加载时,其内部类(包括静态内部类)是不会同时被加载的,只有在使用到的时候,内部类才会被加载”,因此很好的实现了惰性加载。
事后看到一篇文章, 自认为是看到最好的解释, 收藏下
http://www.blogjava.net/zellux/archive/2008/04/07/191365.html
http://www.iteye.com/topic/260515这篇文章使用happen-before来解释了DCL的问题