安卓单例模式的理解

       单例模式的目标:

       1.在调用getInstance()方法时返回一个且唯一的Singleton对象。 
  2.能够在多线程使用时也能保证获取的Singleton对象唯一 
  3.getInstance()方法的性能要保证 
  4.能在需要的时候才初始化,否则不用初始化 

单例模式的几种写法,由浅到深,根据平常的需求选择适当的实现方式。

写法一 饿汉式 :

/**
 * 饿汉式
 * 基于ClassLoader的机制,在同一classLoader下,该方式可以解决多线程同步的问题,
 * 但是该种单例模式没有办法实现懒加载
 */
public class SingletonHungry {
    /**
     * 在ClassLoader加载该类时,就会初始化mInstance
     */
    private static SingletonHungry mInstance = new SingletonHungry();

    private SingletonHungry() {
    }

    public static SingletonHungry getInstance() {
        return mInstance;
    }
 }

根据java虚拟机和ClassLoader的特性,一个类在一个ClassLoader中只会被加载一次,当我们实现SingletonHungry类时,mInstance就已经被实例化了,保证了在多线程并发情况下获取到的对象是唯一,但不适用例如某类实例需求依赖在运行时的参数来生成对象。

 

 写法二 懒加载(非线程安全):

/**
 * 懒汉式
 * 只有在getInstance()时才会初始化mInstance
 * Created by chuck on 17/1/18.
 */
public class SingletonLazy {
    private static SingletonLazy mInstance;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstanceUnLocked() {
        if (mInstance == null) {//line1
            mInstance = new SingletonLazy();//line2
        }
        return mInstance;//line3
    }
}

这种方式无法满足多线程的情况,线程A执行了line1但line2还未完成对象的初始化,线程B执行到line1就直接跳过line2执行line3了,就会出问题,这里可以了解下new SingletonLazy()这个操作不是原子操作,该操作分为:

       1.分配内存空间 
  2.初始化对象 
  3.将对象指向分配好的地址空间(执行完之后就不再是null了) 

其中第2,3步在一些编译器中为了优化单线程中的执行性能是可以重排的。重排之后就是这样的: 
  1.分配内存空间 
  3.将对象指向分配好的地址空间(执行完之后就不再是null了) 
  2.初始化对象 

所以,当重排的时候就会出问题,此方法保证不了线程安全,所以我们加上同步。

 写法三 懒加载(线程安全):

/**
 * 懒汉式
 * 只有在getInstance()时才会初始化mInstance
 * Created by chuck on 17/1/18.
 */
public class SingletonLazy {
    private static SingletonLazy mInstance;

    private SingletonLazy() {

    }

/**
 * 方法名多了Locked表示是线程安全的,没有其他意义
 */
    public synchronized static  SingletonLazy getInstanceLocked() {
        if (mInstance == null) {
            mInstance = new SingletonLazy();
        }
        return mInstance;
    }
 }

该方法加上synchronized关键字保证了线程安全,同一时间两个线程不能同时访问getInstanceLocked()方法,但当有多个线程会频繁调用getInstanceLocked()方法的话会造成很大的性能损失,该方法就比较适合那些没有多线程频繁调用时的场景。为了解决这个问题出现了双重检查锁定(简称DCL)。

写法四 双重检查锁定(DCL):

/**
 * 双重检查锁定DCL
 * Created by chuck on 17/1/18.
 */
public class SingletonLazy {
    private static SingletonLazy mInstance;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        if (mInstance == null) {//第一次检查
            synchronized (SingletonLazy.class) {//加锁
                if (mInstance == null) {//第二次次检查
                    mInstance = new SingletonLazy();//new 一个对象
                }
            }
        }
        return mInstance;
    }
 }

这个方法并发和性能的问题得以解决,但也存在之前说的重排情况,即多线程会获取到一个未初始化的对象。为了解决这个情况,我们引入了volatile关键字:

/**
 * 双重检查锁定DCL
 * Created by chuck on 17/1/18.
 */
public class SingletonLazy {
    private volatile static SingletonLazy mInstance;

    private SingletonLazy() {

    }

    public static SingletonLazy getInstance() {
        if (mInstance == null) {//第一次检查
            synchronized (SingletonLazy.class) {//加锁
                if (mInstance == null) {//第二次次检查
                    mInstance = new SingletonLazy();//new 一个对象
                }
            }
        }
        return mInstance;
    }
 }

volatile关键字会禁止重排,在JDK1.5以上,volatile关键字修饰的变量保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。 禁止进行指令重排序。

写法五 静态内部类:

/**
 * 静态内部类方式实际上是结合了饿汉式和懒汉式的优点的一种方式
 * Created by chuck on 17/1/18.
 */
public class SingletonInner {
    private SingletonInner() {
    }

    /**
     * 在调用getInstance()方法时才会去初始化mInstance
     * 实现了懒加载
     *
     * @return
     */
    public static SingletonInner getInstance() {
        return SingletonInnerHolder.mInstance;
    }

    /**
     * 静态内部类
     * 因为一个ClassLoader下同一个类只会加载一次,保证了并发时不会得到不同的对象
     */
    public static class SingletonInnerHolder {
        public static SingletonInner mInstance = new SingletonInner();
    }
}

这种方法很简便,我们在单例类SingletonInner类中,实现了一个static的内部类SingletonInnerHolder,在内部类SingletonInnerHolder中去实例mInstance,保证我们在需要的时候再去实例化mInstance对象,又由于classLoader对同一个类,只加载一次,我们在加载SingletonInnerHolder类时保证了线程间安全,不管多少线程,得到的也是同一个类,保证了并发下是该方式是可用的,不会出现重排情况,因为下一个线程永远都只能等SingletonInner()实例化完成才能执行,不会拿到未实例化的mInstance,因为classLoader机制。

以上主要参考https://blog.csdn.net/chenkai19920410/article/details/54612505后自己做的一些总结,方便理解单例模式的各种写法,加深印象,更多详细内容请参考原作者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值