Android设计模式之——单例模式之源码使用场景(一)

756 阅读9分钟


以下是五种单例模式,开发中都会用到

一、实例

/**
 * User:xijiufu
 * Time:2016/4/4,0:00
 * Function : 普通员工
 */
public class Staff {

    public void work() {
        //干活
    }
}
/**
 * User:xijiufu
 * Time:2016/4/4,0:01
 * Function :副总裁
 */
public class VP extends Staff {
    @Override
    public void work() {
        //管理下面的人
    }
}
/**
 * User:xijiufu
 * Time:2016/4/4,14:11
 * Function : 恶汉模式
 */
public class CEO extends Staff {

    private static final CEO mCeo = new CEO();

    private CEO() {
    }

    public static CEO getCeo() {
        return mCeo;
    }

    @Override
    public void work() {
        //管理VP
    }
}
/**
 * User:xijiufu
 * Time:2016/4/4,14:12
 * Function :公司
 */
public class Company {

    private List<Staff> allStaffs = new ArrayList<>();

    public void addStaff(Staff per) {
        allStaffs.add(per);
    }

    public void showAllStaffs() {
        for (Staff per : allStaffs) {
            System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:" + per.toString());
        }
    }
}

测试结果:

public class SingletonTest {

    public static void main(String[] strings) {
        Company cp = new Company();
        Staff ceo1 = CEO.getCeo();
        Staff ceo2 = CEO.getCeo();
        cp.addStaff(ceo1);
        cp.addStaff(ceo2);
        Staff vp1 = new VP();
        Staff vp2 = new VP();
        Staff staff1 = new Staff();
        Staff staff2 = new Staff();
        Staff staff3 = new Staff();

        cp.addStaff(vp1);
        cp.addStaff(vp2);
        cp.addStaff(staff1);
        cp.addStaff(staff2);
        cp.addStaff(staff3);

        cp.showAllStaffs();

    }
    
}

输出的结果是:

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.CEO@78308db1
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.CEO@78308db1
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.VP@27c170f0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.VP@5451c3a8
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.Staff@2626b418
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.Staff@5a07e868
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.Staff@76ed5528

从上述的代码中可以看出,CEO类不能通过new的形式构造对象,只能通过CEO.getCEO() 函数来 获取,而这个CEO对象是静态对象,并且在声明的时候就已经初始化了,这就保证了CEO对象的唯一性。从输出的结果可以看出,两次CEO输出的对象都是一样的,VP与Staff对象是不同的。这个实现的核心在于将CEO类的构造方法私有化,使得外部程序不能通过构造函数来构造CEO对象,而CEO类通过一个静态方法返回一个静态对象。

二、单例模式

(1)、懒汉模式

懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化,而上述的饿汉模式是在声明静态对象时就已经初始化,懒汉模式实现如下:

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

根据上面的代码发现getInstance方法中添加了synchronized关键字,也就是getInstance是一个同步方法,这就是上面所说的在多线程情况下保证单例对象唯一性的手段,细想几十instance已经被初始化,第一次调用时就会被初始化instance,每次调用getInstance方法都会进行同步,这样就消耗不必要的资源,这也是懒汉模式存在的问题。

懒汉模式的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源,缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance都进行同步,造成不必要的同步开销,所有不建议使用这种模式。

(2)、Double CheckLock (DCL) 实现单例

DCL方式实现单例模式的优点是既能够在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance不进行同步锁。

/**
 * User:xijiufu
 * Time:2016/4/4,14:37
 * Function :Double  CheckLock   DCL双重检查锁定
 */
public class Singleton2 {

    private static Singleton2 sInstance = null;

    private Singleton2() {
    }

    public void doSomething() {
        System.out.println("so  sth.");
    }

    public static Singleton2 getInstance() {
        if (sInstance == null) {
            synchronized (Singleton2.class) {
                if (sInstance == null) {
                    sInstance = new Singleton2();
                }
            }
        }
        return sInstance;
    }

}

可以看到getInstance方法中对instance进行了两次判空:第一层主要为了避免不必要的同步,第二层的判断则是为了在null的情况下创建实例。

为什么呢?

假设线程A执行到了instance=new Singleton2 ()语句,这里看起来是一句代码,但实际上它并不是一个原子操作,什么是原子操作?可以看这儿(baike.baidu.com/item/原子操作?f…),这句代码最终会被编译成多条汇编指令,大致做了三件事:

①、给Singleton的实例分配内存;

②、调用Singleton()的构造函数,初始化成员字段;

③、将instance对象指向分配的内存空间(此时instance就不是null);

DCL的优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。

缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。

(3)、 静态内部类单例模式

DCL虽然在一定程度上解决了资源消耗、多余的同步、线程安全等问题,但是,它还是在某些情况下出现失效的问题。这个问题被称为双重检查锁定(DCL)失效,在《Java并发编程实践》一书的最后谈到这个问题,并指出这种“优化”是丑陋的,不赞成使用,建议使用如下代码:

/**
 * User:xijiufu
 * Time:2016/4/4,14:47
 * Function : 静态内部类单例模式
 */
public class Singleton3 {
    private Singleton3() {
    }

    public static Singleton3 getInstance() {
        return SingletonHolder.sInstance;
    }
    //静态内部类
    private static class SingletonHolder {
        private static final Singleton3 sInstance = new Singleton3();
    }

}

当第一次加载Singleton类时并不会初始化sInstance,只有在第一次调用Singleton的getInstance方法时才会导致sInstance被初始化。因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能够确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。

(4) 、枚举单例

前面说了三种单例模式实现,但是这些实现方式不是稍显麻烦就是会在某些情况下出问题。还有没有更简单的实现方式呢?下面的代码:

/**
 * User:xijiufu
 * Time:2016/4/4,14:50
 * Function :
 */
public enum SingletonEnum {
    INSTANCE;
    public void doSomething() {
        System.out.println(">>>>>>>>>>>>>>>>do.sth");
    }
}

枚举!

写法简单是枚举单例最大的优点,枚举在Java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。为什么?在上述的几种单例中,在一个情况下它们会出现重新创建对象的情况,那就是反序列化。

通过反序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve,这个方法可以让开发人员控制对象的反序列化。例如:上述几个实例中如果要杜绝单例对象在被饭序列化重新生成对象,那么必须加入如下方法:

private Object readResolve() throws ObjectStreamException {
    return sInstance;
}

也就是readResolve方法中将instance对象返回,而不是默认的重新生成一个新的对象。而对于枚举,并不存在这个问题,因为即使使用反序列化它也不会重新生成新的实例。

(5)、使用容器实现单例模式

通过以上的单例 模式之后 ,再来看看一种另类的实现,代码如下:

/**
 * User:xijiufu
 * Time:2016/4/4,14:55
 * Function :使用容器实现单例模式
 */
public class SingletonManager {

    private static Map<String, Object> objMap = new HashMap<>();

    private SingletonManager() {
    }

    public static void registerService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }

}

在程序的初始化时,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时也可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

不管以哪种形式实现单例模式,他们的核心原理都是讲构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。选择哪种实现方式取决于项目本身,如是否是复杂的并发环境、jdk版本是否过低、单例对象的资源消耗等。

三、Android源码中的单例模式

在Android系统中,我们经常会通过Context获取系统级别的服务,如WindowsManagerService、ActivityManagerService等,更常用的是一个LayoutInflater的类,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候通过Context的getSystemService(String name)获取。