饿汉法html
顾名思义,饿汉法就是在第一次引用该类的时候就建立对象实例,而无论实际是否须要建立。代码以下:java
public class Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; } }
这样作的好处是编写简单,可是没法作到延迟建立对象。可是咱们不少时候都但愿对象能够尽量地延迟加载,从而减少负载,因此就须要下面的懒汉法:android
这种写法是最简单的,由私有构造器和一个公有静态工厂方法构成,在工厂方法中对singleton进行null判断,若是是null就new一个出 来,最后返回singleton对象。这种方法能够实现延时加载,可是有一个致命弱点:线程不安全。若是有两条线程同时调用getSingleton() 方法,就有很大可能致使重复建立对象。web
public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton() { if(singleton == null) singleton = new Singleton(); return singleton; } }
这种写法考虑了线程安全,将对singleton的null判断以及new的部分使用synchronized进行加锁。同时,对 singleton对象使用volatile关键字进行限制,保证其对全部线程的可见性,而且禁止对其进行指令重排序优化。如此便可从语义上保证这种单例 模式写法是线程安全的。注意,这里说的是语义上,实际使用中仍是存在小坑的,会在后文写到。缓存
public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } return singleton; } }
虽然上面这种写法是能够正确运行的,可是其效率低下,仍是没法实际应用。由于每次调用getSingleton()方法,都必须在synchronized这里进行排队,而真正遇到须要new的状况是很是少的。因此,就诞生了第三种写法:安全
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; } }
这种写法被称为“双重检查锁”,顾名思义,就是在getSingleton()方法中,进行两次null检查。看似画蛇添足,但实际上却极大提高了 并发度,进而提高了性能。为何能够提升并发度呢?就像上文说的,在单例中new的状况很是少,绝大多数都是能够并行的读操做。所以在加锁前多进行一次 null检查就能够减小绝大多数的加锁操做,执行效率提升的目的也就达到了。多线程
那么,这种写法是否是绝对安全呢?前面说了,从语义角度来看,并无什么问题。可是其实仍是有坑。说这个坑以前咱们要先来看看volatile这个 关键字。其实这个关键字有两层语义。第一层语义相信你们都比较熟悉,就是可见性。可见性指的是在一个线程中对该变量的修改会立刻由工做内存(Work Memory)写回主内存(Main Memory),因此会立刻反应在其它线程的读取操做中。顺便一提,工做内存和主内存能够近似理解为实际电脑中的高速缓存和主存,工做内存是线程独享的, 主存是线程共享的。volatile的第二层语义是禁止指令重排序优化。你们知道咱们写的代码(尤为是多线程代码),因为编译器优化,在实际执行的时候可 能与咱们编写的顺序不一样。编译器只保证程序执行结果与源代码相同,却不保证明际指令的顺序与源代码相同。这在单线程看起来没什么问题,然而一旦引入多线 程,这种乱序就可能致使严重问题。volatile关键字就能够从语义上解决这个问题。并发
注意,前面反复提到“从语义上讲是没有问题的”,可是很不幸,禁止指令重排优化这条语义直到jdk1.5之后才能正确工做。此前的JDK中即便将变 量声明为volatile也没法彻底避免重排序所致使的问题。因此,在jdk1.5版本前,双重检查锁形式的单例模式是没法保证线程安全的。高并发
那么,有没有一种延时加载,而且能保证线程安全的简单写法呢?咱们能够把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就建立对象,而且因为静态内部类只会被加载一次,因此这种写法也是线程安全的:性能
public class Singleton { private static class Holder { private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getSingleton(){ return Holder.singleton; } }
可是,上面提到的全部实现方式都有两个共同的缺点:
都须要额外的工做(Serializable、transient、readResolve())来实现序列化,不然每次反序列化一个序列化的对象实例时都会建立一个新的实例。
可能会有人使用反射强行调用咱们的私有构造器(若是要避免这种状况,能够修改构造器,让它在建立第二个实例的时候抛异常)。
固然,还有一种更加优雅的方法来实现单例模式,那就是枚举写法:
public enum Singleton { INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } }
使用枚举除了线程安全和防止反射强行调用构造器以外,还提供了自动序列化机制,防止反序列化的时候建立新的对象。所以,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的语义问题,还加入了很多锁优化,使得多线程同步的开销下降很多)。
最后,无论采起何种方案,请时刻牢记单例的三大要点:
线程安全
延迟加载
序列化与反序列化安全
《Effective Java(第二版)》
《深刻理解Java虚拟机——JVM高级特性与最佳实践(第二版)》
原文参考:http://www.tekbroaden.com/singleton-java.html