读书笔记(一)---单例模式

朋友介绍的,买了本《Android源码设计模式解析与实战》,趁现在有时间,好好啃啃,做做记录。
对于单例模式,我相信基本上每个程序员都知道怎么写了,但是懒汉式与饿汉式的区别,DCK(Double Check Lock)有没有问题,静态内部类单例模式和枚举单例,是否每个人都知道呢?

定义

说得简单点,就是我系统项目里,这个类的对象也能出现一次,它不能被外部实例化,可以向系统提供获取这个实例的方法。

关键点

依据定义,基本需要明白单例模式的关键点了:
1.外部不能实例化,所以其构造函数是private的[当然这是针对Java大法说得]
2.需要通过一个静态方法或者枚举返回单例类对象
3.需要确保单例类的对象有且只有一个,特别是在多线程的情况下
4.单例对象在反序列化时不会重新new对象【这个不是很清楚,等会写个代码试一下】

饿汉式代码

以Person为例,毕竟我们经常用Person:

public class Person {

    private static Person instance = new Person() ;

    private Person() {}

    public static Person getInstance(){
        return instance ;
    }
}

我理解的饿汉式,就是不管三七二十一,我一上来就给你初始化掉,我不管你用不用,这样确实在多线程环境下我能保证单例对象的唯一性,因为它伴随着class文件加入内存,此时就已经初始化了对象。唯一的不足就是这货始终占着内存。

懒汉式代码

public class Person {

    private static Person instance ;

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

    private Person() {

    }

}

当然了懒汉式虽然在需要用到的时候,才会去构建对象,确实会节省内存,但是因为synchronized 关键字,在多线程环境下,每次获取对象,同步也会消耗资源。当然了,很多哥们在工作都不会遇到啥多线程高并发,但是我们要建立这方面思考,兴许哪一天你真的用上了呢。

综上,无论是懒汉还是饿汉,都有自己的好处和不足,这也就看你个人的想法了,想用哪个自己选吧

DCL[double check Lock]

什么叫DCL,双重锁定,看看代码,我想很多人都用过,看上去逼格比较高:

public class Person {

    private static Person instance ;

    public Person getInstance() {
        if(instance == null) {
            synchronized(Person.class) {
                if(instance == null)
                    instance = new Person() ;
            }
        }
        return instance ;
    }

    private Person() {
    }

}

DCK的好处是,它做到了需要用的时候才会初始化,也保证了线程安全,此时getInstance方法没有synchronized关键字修饰,表明每次获取单例对象时,不需要进行同步。但是在JDK1.5之前,存在一些问题,为什么呢???

当然了,第一次看到这种代码,会对第二个为null判断有些不懂,我也一样,第一次看到这种代码时,感觉有些多余,直到了看到了本书,我会用自己的语言来验证一下我是否真的懂了。

Person instance = new Person();

这个赋值,初始化代码,很明显不是原子性操作[所谓原子性操作,我举个简单例子,你一个人吃完一张大饼,这就是原子操作,换做Java语言讲,我执行这条语句时,中间不会有其他线程打扰我的操作,不会进行线程的切换,就像你吃大饼的过程中,其他人不能吃,女朋友男朋友除外啊,具体可以参见百度],那么这条语句,可以解析成:
A: 给instance分配内存
B:初始化Person,即调用construct函数,初始化属性字段
C:给new Peson()对象找个句柄,就是引用吧,然后把这个引用交给instance(instance此时就不会能为空了)

Java编辑器是不是按照我们的想法执行那行代码呢? 这个应该和Java版本有关。由于Java编辑器可以支持乱序执行【不要问我怎么知道,你可以看看有关Class类的知识,比较蛋疼,还有疑问,Java中有个保留关键字goto,这个关键字之所以现在不用了,就是它可以到处跳,把代码搞得乱乱的,代码层级上都可以支持乱序执行,我觉得编译之后的代码也可以吧】和JDK1.5之前JMM(java Memory Model java内存模型)中的Cache,寄存器到主内存回写的顺序,那么B和C代码执行的顺序,可能是C先,也可能是B先,那么代码的执行顺序可能为:
A—-B—-C
A—-C—-B
如果是A—C—-B顺序,那么此时如果刚好有线程执行到C语句【此时instance已经被赋值了】,此时切换到另外线程,在instance不为空的情况下,将会返回instance所指的引用对象,但是其指向的 堆里面的对象 是不存在的,此时就是我们的问题所在了。

这就是DCL唯一的不足了吧,但是如果是JDK1.5之后,加上volatile关键字,即:

private volatile static Person instance ;

它可以确保instance每次都会从主内存中读取,就可以用DCL模式完成单例模型,但还是那个问题,volatile关键字与线程有关,肯定也是会牺牲内存的。

最后总结一下DCL,它优点是资源利用率非常高,第一次执行getInstance时才会被实例化;缺点是第一次反应稍慢,Java内存模型的设计也会有一定的几率在高并发的情况下使得单例模式失效。

当然了,这种DCL模式也是我们写的最多了,相比于懒汉和饿汉,逼格不是高了一点点,它能够在需要时才实例化单例对象,并且能够在绝大多数场景中保证单例对象的唯一性,如果不是并发异常复杂和JDK_version>1.6,我劝你用这种方法,比较高端啊。

静态内部类单例模式

来自《Java并发编程》,建议使用一下方法:

public class Singleton {

    private Singleton() {}

    //返回值来自其内部类中赋值
    public static Singleton getInstance() {
        return SingletonHolder.singletion ;
    }

    private static class SingletonHolder {
        private static final Singleton singletion = new Singleton() ;
    }

}

第一次加载Singleton类时并不会初始化SingletonHolder中的singletion,只有在第一次调用Singletion的getInstance方法时,才会导致singletion的初始化。因此第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅仅能够保证线程安全,也能保证单例对象的唯一性,同事也延长了单例实例化,所谓高手就是高手,本方法比较屌啊!!!

枚举单例

非常简单的实现方式,说实话我从来没用过,今天这代码一些,感觉好枚举好强啊:

public enum EnumSingleton {

    SINGLETION ;

    private String name;

    public void say() {
        System.out.println("hello world!!");
    }

}

//Test.java
public static void main(String[] args) {
    EnumSingletion.SINGLETION.say();
}

枚举是一个很强大的类,就是和类一模一样,属性方法一个都没少,最重要的事默认枚举实例的创建时线程安全的,并且在任何条件下都是一个单例。
而且枚举相比较其他几种单例写法,不会出现重新创建对象的情况。那什么情况下可以会重新创建对象呢?反序列化!!

通过序列化可以将一个单例函数的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例。即使构造函数是private的,反序列化依然可以可以通过特殊途径去创建一个新的实例,相当于调用了该类的构造函数。反序列化提供了一个很特别的钩子函数,类中具有一个私有的、实例化的方法readResolve();这个方法可以让开发人员控制对象的反序列化。举个例子,看代码:

还是那个person类:

        Person  p = Person.getInstance() ;
        System.out.println("p1: " + p);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(new File("person.data"))) ;

        out.writeObject(Person.getInstance());
        out.close(); 

        System.out.println("------------------------------------");

        ObjectInputStream input = new ObjectInputStream(new FileInputStream(new File("person.data"))) ;
        Person p2 = (Person) input.readObject();
        input.close();

        System.out.println("p2: " + p2);

        ObjectInputStream input3 = new ObjectInputStream(new FileInputStream(new File("person.data"))) ;
        Person p3 = (Person) input3.readObject();
        input3.close();

        System.out.println("p3: " + p3);

可以看打印值:
读书笔记(一)---单例模式_第1张图片

可以看出,两次反序列化,我们的单例模式被重复赋值,变成了两个不同的对象了,这个就完全违背了我们单例模式的初衷了。

现在在Person类中加入readResolve()方法:

public class Person implements java.io.Serializable{

    private static final long serialVersionUID = 2670093168396274501L;

    private static Person instance ;

    private String mName = "zhangsan" ;

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

    private Person() {}

    public String getName() {
        return mName ;
    }

    //use in unSerializable
    private Object readResolve() throws ObjectStreamException {
        return instance ;
    }

}

重复打印:
读书笔记(一)---单例模式_第2张图片
此时我们发现,二者生成的对象是同一个对象了,这就满足我们的需求了。

容器单例模式

看代码,这种代码将多种单例模式注入容器,使用容器统一管理,并且在使用时通过统一的接口进行获取操作,降低了用户的使用成本,也对用户实现了隐藏具体实现,降低了耦合度。
举个栗子:

/**
 * 容器单例模式
 * @author guoli
 */
public class ContainerSingleton {

    private static Map mContainer = new HashMap();

    private ContainerSingleton(){
    }

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

    public static Object getValue(String key) {
        return mContainer.get(key) ;
    }

}

综上

不管哪种形式实现单例模式,它们的核心都是构造私有化,静态获取,获取实例时需保证线程安全,最后还需要考虑反序列化时生成多个实例问题。
当然了,选择基本靠自己了,哪种写法都是自己说得对了啊。。。。

你可能感兴趣的:(android进阶,工具类)