越来越感觉编程的世界是真正符合自然的世界,真正遵循自然的规律。在程序的世界中事情几乎是平等的,即使不平等也是有据可循,任何技术问题也可讨论的,无论结果如何,总是0与1的差别。 同样真实世界中的事物放到程序世界中也变得那么客观起来,正如这篇文章的主角Singleton Design Pattern一样。
单例模式可以有多种实现方法,需要根据情况作出正确的选择。
- 饿汉式
public class Singleton {
private static Singleton mSingleton = new Singleton();
public static Singleton getInstance() {
return mSingleton;
}
}
缺点:在类加载的时候就完成了初始化,所以类加载比较慢
- 懒汉式
public class Singleton {
private static Singleton mSingleton;
public static Singleton getInstance() {
if (mSingleton == null) {
mSingleton = new Singleton();
}
return mSingleton;
}
}
缺点:在多线程情况上不能正常工作
- 双重检查锁定(double-checked locking)
public class Singleton {
private static Singleton mSingleton;
public static Singleton getInstance() {
if (mSingleton == null) {
synchronized (Singleton.class) {
if (mSingleton == null) {
mSingleton = new Singleton();
}
}
}
return mSingleton;
}
}
缺点:在多线程运行的时候,mSingleton可能报空指针的错误。假设线程A运行getInstance()方法,当运行 mSingleton = new Singleton()的时候,线程B也调用getInstance()方法,因为mSingleton !=null(但实际上初始化有问题),直接返回mSingleton ,导致空指针问题。WTF?
这是因为 mSingleton = new Singleton()这一步操作不是原子性操作(所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch ),在执行的过程中,需要3步处理1.mSingleton 分配内存 2.mSingleton 调用起构造函数 3.对mSingleton 指向内存分配区域 1--2--3,但有点时候jvm会将其重排序,可能1--3--2,在线程B调用的时候,虽然mSingleton 不是null,但它可能没有进行初始化,导致空指针。
- volatile关键字
volatile能解决上面这种问题
private volatile static Singleton mSingleton;
mSingleton现在由volatile 关键字修饰,禁止重排序并且保证了变量的可见性。
- 静态内部类
public class Singleton {
public static Singleton getInstance() {
return Holder.mSingleton;
}
private static class Holder {
private static Singleton mSingleton = new Singleton();
}
}
内部类分为对象级别和类级别,类级内部类指的是:有static修饰的成员变量的内部类。如果没有static修饰的成员变量的内部类成为对象级内部类。
类级内部类相当于外部类static成员,不存在依赖关系,相互独立,只有在第一次使用时才会被初始化。
当调用getInstance() 方法时,内部类Holder类才得到初始化;Singleton实例属于静态的域,由虚拟机来保证它的线程安全。
这个模式优势在于,getInstance()方法并没有被同步,只是执行一个域的访问,因为延时初始化并没有增加任何访问成本。