以下是五种单例模式,开发中都会用到
一、实例
/**
* 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)获取。