单例模式算是比较常用的模式,在Java中如果想要一个JVM中只存在某个类的一个实例,就需要使用到单例模式,而只存在一个实例的需求一般是因为:
1,对象实例比较大和复杂,创建开销很大。
2,只需要一个实例来维护整个功能的流程与交互。
例如Android中的电话应用启动时,对于单卡单待的电话,只创建一个Phone对象,用来管理RIL,CallTracker,ServiceStateTracker等对象,手机中不存在第二个Phone对象去和RILC通信。
单例的类图很简单,看起来也很简单,只需要一个类只能得到一个实例即可,但是我觉得单例比其他创建型的模式要复杂的多。
如果想要创建一个类Singleton。正常来讲只需要new Singleton()即可,但是如果想Singleton只存在一个实例,则不能采用这种方法来创建,因为每一次new都会产生一个新的实例:
1,为了不能使用new创建,就要把构造函数变成private的;
2, 为了只有一个实例,就需要Singleton本身去维护这个实例,于是类中需要定义一个Singleton instance,显然它应该是private的,因为它是类的实例,不是对象的实例,所以它还应该是静态的;
3,因为不能new,为了能够有方法得到Singleton的实例,就得通过一个静态方法返回实例,比如public static Singleton getInstance(),在内部如果instance是null的则新建,否则返回instance即可,
说了这么多其实需要注意的东西还是蛮多的,根据上面的分析,已经能够创建出一个最简单单例模式了。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
// Nothing
}
public static Singleton getInstance(){
return instance;
}
public void say(){
System.out.println("I am singleton");
}
}
饿汉单例会在类装载时就实例化。好处就是由于classloder机制,保证当一个类被加载的时候,这个类的加载是线程互斥的,而饿汉单例的静态instance直接新建一个实例,在加载的时候就能线程安全的获得实例。从而避免了线程安全问题。
劣势就是实例在装载的时候就会浪费资源和时间去实例化。虽然大多数时候都是在调用getInstance时才会装载,不过也没法保证是否有其他方法会使用到instance而导致新建实例。
这个Singleton单例类在被加载的时候就会创建实例,为了让它在使用的时候才创建对象所以把它设为null。让它在getInstace的时候再新建实例,也就是惰性加载。这里也叫懒汉式单例。
public class lazySingleton {
private static lazySingleton instance = null;
private lazySingleton() {
// TODO Auto-generated constructor stub
}
public static lazySingleton getInstace() {
if (instance==null){
instance =new lazySingleton();
}
return instance;
}
public void say(){
System.out.println("I am lazySingleton");
}
}
前面的懒汉例子用在单线程中不会出现问题,但是如果用在单线程中就会出现问题。如果AB两个线程都通过getInstace去获取单例的实例,因为没法保证getInstace方法会在一个线程中一直执行完再执行另一个线程,如果两个线程都判定instance位null,则有可能都会进入new语句新建实例。
为了保证同一时刻只有一个线程能够执行getInstance,就需要对方法加锁,或者在方法内部使用对象锁,但是注意要把锁放在if(instance == null) 判断的外面,否则还是可能出现同时判定true的情况。
public class SyncSingleton {
private static SyncSingleton instance = null;
private SyncSingleton() {
// TODO Auto-generated constructor stub
}
public static synchronized SyncSingleton getInstance(){
if (instance == null){
instance = new SyncSingleton();
}
return instance;
}
}
或者
public class SyncSingleton {
private static SyncSingleton instance = null;
private static final Object classLock = SyncSingleton.class;
private SyncSingleton(){}
public SyncSingleton getInstance(){
synchronized(classLock){
if(instance == null)
instance = new SyncSingleton();
return instance;
}
}
}
上面的问题解决了多线程的问题,但是也会带来性能的问题,因为每次调用getInstance,都会进行同步,但实际上,如果instance实例已经建立,那直接返回instance实例就好,这里是不用加锁的,只有新建实例的情况才需要同步锁,但是前面也说到了,新建的时候要把锁放在if(instance == null) 判断的外面,否则还是可能出现同时判定true的情况。所以就有了下面的双重检查的单例模式
public class EffectiveSingleton {
private volatile static EffectiveSingleton instance = null;
private EffectiveSingleton() {
// TODO Auto-generated constructor stub
}
public static EffectiveSingleton getInstance() {
if (instance == null) { // 首先判断是否已经创建实例,如果已经创建,直接返回,效率高
synchronized (EffectiveSingleton.class) {// 如果没有创建,然后再同步,并在同步块内再初始化。注意要再次判断是否已实例化
if (instance == null) {
instance = new EffectiveSingleton();
}
}
}
return instance;
}
}
这样当实例初始化已经完成的情况,每次getInstance直接返回即可,不再需要同步锁。
当第一次调用getInstance时,则通过synchronized块包裹的代码部分保证不会多次调用新建实例。
第二个if (instance == null) 条件判断是为了第一个if (instance == null) 是给已经存在实例的情况用的,AB线程还是有可能都通过第一个条件判断的。这就需要在同步块内,一定要有一个条件判断。
双重检查加锁单例模式很好地解决了加锁单例的性能问题。
静态内部类也叫嵌套类,用这个名字给他定义是更加形象的。意思是说内部类和外部类的关系只是层次嵌套关系,所以只是在创建类文件的时候类文件名是如下形式:outer$inner.java,在使用方面完全和两个普通类一样。
在饿汉单例的基础上,把instance = new Singleton()外用一个叫做SingletonHolder 的静态内部类包装一下。
这样即使在Singleton类加载的时候,也不会导致静态内部类SingletonHolder 的加载,只有在使用到SingletonHolder 的时候,才会加载。
同时由于类的加载的机制的互斥性,保证创建实例时候的线程安全性。
class Singleton {
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
private Singleton() {
}
public static final Singleton getInstance() {
return SingletonHolder.instance;
}
}
通过PhoneFactory创建Phone对象的例子,截取部分如下:
public class PhoneFactory {
static private Phone sProxyPhone = null;
......
public static void makeDefaultPhone(Context context) {
synchronized(Phone.class) {
if (!sMadeDefaults) {
...
sProxyPhone = new PhoneProxy(new GSMPhone(context,
sCommandsInterface, sPhoneNotifier));
...
sMadeDefaults = true;
}
}
}
可以看出这个例子符合上面的加锁单例模式,虽然不是采用的双重判断的方式来增加效率,但是因为PhoneFactory的makeDefaultPhone基本没有多线程使用情况,只有在Phone应用启动的情况下调用一起。
并且PhoneFactory通过makeDefaultPhone来创建实例,但是却使用getDefaultPhone来获取实例,也就不存在实例已经存在的情况下,还进入同步块进行判断的情况。