Jianwoo中的设计模式(1) — 单例模式

前言

停更了几天,刚好碰上端午节,出去玩了下,下面开始关于Jianwoo的项目分析的第二部分,Jianwoo中所用到的设计模式

关于单例模式

单例模式是一种很常见的设计模式,你可以说它是一个很简单的设计模式,也可以说是一个很复杂的设计模式。对于前者而言你只需要写一个类就可以完成单例模式的创建,所以它很“简单”,但是如果你要对对象创建的次数以及对象何时被创建以及封装多种样式适应不同场景的单例模式进行深究,那也可以是一个不简单的模式

那我们用单例模式,无非就两个目的:1、对象只能创建一次 2、提供一个统一的访问入口(如getInstance),目的很简单,结构也很简单,但我们还得避免以下问题

  • 1、保证统一返回入口并且每次调用返回是唯一的一个对象
  • 2、防止从别的入口实例化对象
  • 3、多线程访问,如何保证统一入口的访问不会被实例化多次
  • 4、在保证多线程访问无误时,如何提高访问性能

简物中的单例模式

先看Jianwoo中的场景需求之一:封装一个单例模式的SharedPeferenceUtil的工具类

/**
 * Created by zlh/Barry/狗蛋哥 on 2016/11/7.
 * SharedPreferences单例类
 */
public class SharedPreferencesUtils {

    private static SharedPreferencesUtils INSTANCE;
    private SharedPreferences mSp;
    private SharedPreferences.Editor mEditor;
    private Context mContext;


    String tag = getClass().getSimpleName();

    /**
     * 防止默认构造器被调用需私有化
     */
    private SharedPreferencesUtils(){
    }

    /**
     * 注意此处传入的Context应该避免传入Activity的引用
     * 单例模式中的静态对象存在于方法区,生命周期和Application一样存在于整个进程的生命周期
     * 如果没有被销毁或者赋值为null,其对象会一直被保持被引用
     * 也就是如果你传入的Activity的引用,将会引起内存泄漏,导致Activity的资源无法释放
     * 这里可以传入Application的引用,因为Application的生命周期本身就是跟随进程,进程销毁时才跟随销毁
     * @param context
     */
    private SharedPreferencesUtils(Context context){
        mContext = context.getApplicationContext();
        mSp = context.getSharedPreferences(SharedPreferencesConfig.NAME, SharedPreferencesConfig.MODE);
        mEditor = mSp.edit();
    }
}

现在这个类是没有单例模式的方法,但是其解决了上面写到问题的第2点:防止从别的入口实例化对象,因为类中的构造函数都被私有化了(这里暂不套路如何避免利用反射等方式实例化对象)
注:这里涉及到一个内存泄漏的问题

在单利模式中,如果包含Context引用,构造方法传入的Context应该避免传入Activity的引用,单例模式中的静态对象遵循于java存在于方法区,生命周期和Application一样存在于整个进程的生命周期,如果没有被销毁或者赋值为null,其对象会一直被保持被引用,也就是如果你传入的是Activity的引用,将有可能会引起内存泄漏,导致Activity的资源无法释放。那这里可以传入Application的引用,因为Application的生命周期本身就是跟随进程,进程销毁时才跟随销毁,并且不会造成内存泄漏

接下来我们按照统一入口这一点写一个方法来提供对象创建和引用

    public static SharedPreferencesUtils getInstance(Context context){
            if(INSTANCE == null){
                INSTANCE = new SharedPreferencesUtils(context);
            }
            return INSTANCE;
    }

这样写已经解决了问题1中的一部分:提供统一的入口创建对象,但是并没有解决另一半:每次调用都是返回唯一一个对象。为什么可能返回的不是唯一的对象,这就涉及到了问题3:多线程访问,如何保证统一入口的访问不会被实例化多次,试想一下:假设有两个线程并发访问,线程一刚刚执行完if(INSTANCE == null),还未执行INSTANCE = new SharedPreferencesUtils(context); 而这个时候CPU把资源分配给了线程二,因为线程一还未执行INSTANCE的实例化,INSTANE还是null,所以线程二执行了INSTANCE = new SharedPreferencesUtils(context); 那这个时候线程一又被分配到了资源,刚刚已经执行过了 if(INSTANCE == null) 不会二次判断,于是执行INSTANCE = new SharedPreferencesUtils(context); 结果:INSTANCE被实例化了两次,两次调用返回的INSTANCE并不是同一个对象
下面针对以上问题修改一下

    public synchronized static SharedPreferencesUtils getInstance(Context context){
        if(INSTANCE == null){
            INSTANCE = new SharedPreferencesUtils(context);
        }
        return INSTANCE;
    }

这里以来,解决了线程并发问题,两个线程同时调用这个入口方法,线程一先拿到资源,执行了方法,线程二在准备调用,不会出现两个线程同时进入并且实例化对象的问题,看起来似乎很完美,但是这个时候引出了第4个问题:在保证多线程访问无误时,如何提高访问性能
为什么说这样的设计存在性能问题?这个方法中,if(INSTANCE == null)如果不为null,直接返回INSTANCE是不耗时的,这个方法调用耗时在于synchronized修饰符的同步准备上,也就是本来我们只需要防止INSTANCE = new SharedPreferencesUtils(context); 被同时执行,现在变成了INSTANCE不为null的情况下也要让线程等待整个方法被执行完
那我们针对这个问题进行修改

    public static SharedPreferencesUtils getInstance(Context context){
        if(INSTANCE == null){
            synchronized (SharedPreferencesUtils.class){
                if(INSTANCE == null){
                    INSTANCE = new SharedPreferencesUtils(context);
                }
            }
        }
        return INSTANCE;
    }

线程一执行完了if(INSTANCE == null) 进入synchronized (SharedPreferencesUtils.class) 加锁,现在只有线程一能执行INSTANCE实例化操作,防止了多个线程同时实例化对象。对象实例化完了,以后不管有几次线程的getInstance调用,在判断完if(INSTANCE == null)直接返回INSTANCE,无需加锁等待,解决了问题 4
整体代码以及使用方式如下

/**
 * Created by zlh/Barry/狗蛋哥 on 2016/11/7.
 * SharedPreferences单例类
 */
public class SharedPreferencesUtils {

    private static SharedPreferencesUtils INSTANCE;
    private SharedPreferences mSp;
    private SharedPreferences.Editor mEditor;
    private Context mContext;

    String tag = getClass().getSimpleName();

    /**
     * 防止默认构造器被调用需私有化
     */
    private SharedPreferencesUtils(){
    }

    /**
     * 注意此处传入的Context应该避免传入Activity的引用
     * 单例模式中的静态对象存在于方法区,生命周期和Application一样存在于整个进程的生命周期
     * 如果没有被销毁或者赋值为null,其对象会一直被保持被引用
     * 也就是如果你传入的Activity的引用,将会引起内存泄漏,导致Activity的资源无法释放
     * 这里可以传入Application的引用,因为Application的生命周期本身就是跟随进程,进程销毁时才跟随销毁
     * @param context
     */
    private SharedPreferencesUtils(Context context){
        mContext = context;
        mSp = context.getSharedPreferences(SharedPreferencesConfig.NAME, SharedPreferencesConfig.MODE);
        mEditor = mSp.edit();
    }

    public static SharedPreferencesUtils getInstance(Context context){
        if(INSTANCE == null){
            synchronized (SharedPreferencesUtils.class){
                if(INSTANCE == null){
                    INSTANCE = new SharedPreferencesUtils(context);
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 登录
     * @param username 手机号
     * @param password 密码 md5
     */
    public void saveUserNameAndUserPassword(String username, String password){
        mEditor.putString(SharedPreferencesConfig.MOBILE, username);
        mEditor.putString(SharedPreferencesConfig.PASSWORD, password);
        mEditor.commit();
    }

    /**
     * 获取本地保存的用户名
     * @return
     */
    public String getUserName(){
        return mSp.getString(SharedPreferencesConfig.MOBILE, "");
    }

    /**
     * 获取本地保存的密码
     * @return
     */
    public String getPassword(){
        return mSp.getString(SharedPreferencesConfig.PASSWORD, "");
    }

    /**
     * 退出登录
     */
    public void clearUserNameAndUserPassword(){
        mEditor.putString(SharedPreferencesConfig.MOBILE, "");
        mEditor.putString(SharedPreferencesConfig.PASSWORD, "");
        mEditor.commit();
    }

    /**
     * 是否有本地用户
     * @return
     */
    public boolean hasSave(){
        return !"".equals(getUserName()) && !"".equals(getPassword());
    }
   ...
}

当然单例模式的写法远不止这些,Android中也广泛用到了单利模式,例如我们经常用的getSystemService就是一个带参数的单利模式封装,等等等等…
下一个设计模式见
Jianwoo中的设计模式(2) — 观察者模式

你可能感兴趣的:(Jianwoo中的设计模式(1) — 单例模式)