Android开发经验谈Android开发Android知识

重学设计模式之单例模式

2017-08-31  本文已影响78人  晨鸣code

单例模式应该是大家最为熟知的一种设计模式了,相信大家或多或少的都在自己的项目中使用过单例模式,例如封装一个Log工具类、一个数据库存取类或者用户登录管理类等。而我们使用单例模式主要有两个目的:

  1. 减少内存消耗
  2. 保证某些共享资源的唯一性

单例模式的写法有好多种,如:饿汉式单例模式、懒汉式单例模式、IoDH单例模式、枚举式单例模式,下面就来一一实现下这些写法。

饿汉式单例模式

public class HungrySingletonV1 {

    private static HungrySingletonV1 instance = new HungrySingletonV1();

    private HungrySingletonV1(){
    }

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

上面的例子中,类被加载时,静态变量instance就会被初始化,这时候单例类的唯一实例就被创建了。

饿汉式单例模式还要一种变种

public class HungrySingletonV2 {

    private static HungrySingletonV2 instance;

    static {
        instance = new HungrySingletonV2();
    }

    private HungrySingletonV2(){
    }
    
    public static HungrySingletonV2 getInstance(){
        return instance;
    }
}

效果与前面的一样,不过把初始化方法放到静态代码块中,也是在类加载时调用。

懒汉式单例模式

低配版

public class LazySingletonV1 {

    private static LazySingletonV1 instance ;

    private LazySingletonV1(){
    }

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

上面的代码是最简单的懒汉式单例模式,实现懒加载,每次获取实例时会去判断是否已创建实例,如果一直没人用,则不用创建实例,节省内存空间。

但实际上,上面这种写法应该是最不推荐的一种单例模式写法。因为它是线程不安全的,如果多个线程同时获取该实例,就会创建多个实例对象,不符合单例的需求。

进阶版

public class LazySingletonV2 {

    private static LazySingletonV2 instance ;

    private LazySingletonV2(){
    }

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

可以看到在这种懒汉式单例模式中,我们在获取实例的方法上加了一个同步锁,这样保证了获取实例的方法在不同线程中是同步的,使得获取的实例是唯一的。

但是这种写法有一个最大的问题就是,效率太低。每一个线程都要进行等待,而实际中,如果已经创建了,后面的想获取实例,直接返回就行。

再进阶版

public class LazySingletonV3 {
    private volatile static LazySingletonV3 instance;

    private LazySingletonV3() {

    }

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

基于前一个版本的问题,有人想到了将同步锁置于instance为空判断之后,这样就实现了当实例已经创建,后面的获取时直接返回的问题。

但是,这种写法又导致了线程不安全。如果A用户拿到了同步锁,正在创建实例,另一个B用户在实例还未创建时到了同步锁外等候,当A用户创建完实例,退出同步锁后,B用户马上就获取了同步锁并开始创建实例,这就导致了创建了多个实例。

终极版

//双重校验锁
public class LazySingletonV4 {
    private  volatile static LazySingletonV4 instance;

    private LazySingletonV4(){

    }

    public static LazySingletonV4 getInstance(){
        //检查实例是否存在,不存在才进入同步块
        if(instance == null){
            //同步块,保证线程安全
            synchronized(LazySingletonV4.class){
                //再次检查实例是否存在,不存在才创建实例
                if(instance == null){
                    instance = new LazySingletonV4();
                }
            }
        }
        return instance;
    }
}

双重校验算是懒汉式单例模式的终极版本了,先判断实例是否为空,为空获取同步锁,在同步锁内再判断一次实例是否为空,既保证了线程安全又提高了效率。

双重校验加锁的实现一般会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

IoDH单例模式

什么是IoDH呢?

IoDH是Initialization Demand Holder 的缩写,简单来说就是在单例类中增加一个静态内部类,在该内部类中创建单例类的实例。

我们知道,在多线程开发时,为了解决并发问题,我们会使用synchronized来加互斥锁进行同步控制。但是在某些情况下,JVM已经隐含为您执行了同步,这些时候就不需要自己进行同步控制了。这些情况包括:

  1. 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
  2. 访问final字段时
  3. 在创建线程之前创建对象时
  4. 线程可以看见它将要处理的对象时

例如饿汉式单例模式,就是在类加载时进行了初始化,也就是由静态初始化器初始化的。但是饿汉式单例模式不符合懒加载的要求,如果可以让类加载时不去初始化对象,不就解决问题了吗。这就是IoDH的方法,通过定义一个静态内部类,在这个静态内部类中创建单例类实例,当我们需要使用时才会去加载这个静态内部类,创建单例类实例。

public class IoDHSingleton {

    private IoDHSingleton() {

    }

    private static class SingletonHolder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static IoDHSingleton instance = new IoDHSingleton();
    }

    public static IoDHSingleton getInstance() {
        return SingletonHolder.instance;
    }
}

当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建IoDHSingleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

枚举式单例模式

借助JDK1.5中添加的枚举来实现单例模式,应该是最好的实现单例模式的方式,代码也很简单。

public enum EnumSingleton {

    instance;


    public void method(){
        //功能方法
    }
}

访问也很简单,通过EnumSingleton.instance即可调用枚举类中的方法了。这种单例模式不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

总结

在实际项目中 饿汉式单例模式、懒汉式单例模式终极版、IoDH单例模式、枚举式单例模式 都是可以选择的实现方式,看个人喜好。不过枚举式单例模式应该是最简单且安全的实现方式,推荐使用。

上一篇下一篇

猜你喜欢

热点阅读