饿了就要吃,所以我不能等待了,你要立刻给我一个对象,所以饿汉式是在类加载时就创建对象(方便记忆)。
public class Singleton { private static final Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; } }1.无法做到延迟创建对象,不使用该单例时,你还创建一个对象,不是白白浪费内存吗?
哎呀,你暂时不使用,我就不创建了,你用的时候,我再给创建一个对象。这个就是懒汉式了,你不用我不创建对象,非要等到你使用我再创建对象,一个字:懒(方便记忆)。
由私有构造器和一个公有静态工厂方法构成,在工厂方法中对 singleton 进行 null 判断,如果是 null 就 new 一个出来,最后返回 singleton 对象。这种方法可以实现延时加载
volatile关键字可以保证了线程每次拿到的singleton对象是最新的(详解可见参考网站)。在 getSingleton() 方法中,对singleton的判空和创建对象,做了一个加锁(synchronized),看似完美实则有坑,欲知详情,客官接着看便是。
在“锁”外面再添加一层判断
public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }1.volatile 关键字有坑
volatile的作用一:volatile关键字可以保证了线程每次拿到的singleton对象是最新的; 在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。volatile 的第二层语义是禁止指令重排序优化。大家知道我们写的代码(尤其是多线程代码),由于编译器优化,在实际执行的时候可能与我们编写的顺序不同。 volatile的作用二:禁止指令重排序优化,有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置)。但是很不幸,禁止指令重排优化这条语义直到 jdk1.5 以后才能正确工作。此前的 JDK 中即使将变量声明为 volatile 也无法完全避免重排序所导致的问题。所以,在 jdk1.5 版本前,双重检查锁形式的单例模式是无法保证线程安全的。
上面提到的所有实现方式都有两个共同的缺点
都需要额外的工作 (Serializable、transient、readResolve()) 来实现序列化,否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。可能会有人使用反射强行调用我们的私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。枚举写法 当然,还有一种更加优雅的方法来实现单例模式,那就是枚举写法。使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java 推荐尽可能地使用枚举来实现单例。
代码没有一劳永逸的写法,只有在特定条件下最合适的写法。在不同的平台、不同的开发环境(尤其是 jdk 版本)下,自然有不同的最优解(或者说较优解)。比如枚举,虽然 Effective Java 中推荐使用,但是在 Android 平台上却是不被推荐的。在这篇 Android Training 中明确指出:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
再比如双重检查锁法,不能在 jdk1.5 之前使用,而在 Android 平台上使用就比较放心了(一般 Android 都是 jdk1.6 以上了,不仅修正了 volatile 的语义问题,还加入了不少锁优化,使得多线程同步的开销降低不少)。
如果不考虑延迟加载问题,个人认为直接用饿汉式就好。
最后,不管采取何种方案,请时刻牢记单例的三大要点:
线程安全延迟加载序列化与反序列化安全备注:本文如有任何内容问题,请各位客官及时指正,要改要删,亲亲客官说了算。
