单例模式

介绍

单例设计模式(Singleton)用于保证一个类在整个程序中只有一个实例,通常我们会把设计为单例的类的构造设计成私有的,但不代表所有的单例模式的类的构造都是私有的;
本文的主要内容分为:

  1. 分析常见的单例形式;
  2. 使用懒汉式(DCL)实现一个 AppManager 管理工具类;
  3. 分析 volatile 关键字的作用;

常见的单例的形式

常见的单例设计模式的形式有:

  1. 饿汉式
    饿汉式的单例如果该类被加载了就会创建该单例对象,能够避免多线程并发的问题,但是如果我们的类被加载了(如果有其它的方式,如静态方法等),该单例对象就会被创建,因此 饿汉模式 不是我们的最优方式;
public class AppManager {
    // 在类被加载的时候就创建好了对象本身
    private static AppManager mInstance = new AppManager();
    // 私有化构造器
    private AppManager() {}
    // 提供一个外部访问单例的方法
    public static AppManager getInstance() {
        return mInstance;
    }
}
  1. 懒汉式(DCL)
    这里就不做懒汉形式的线程不安全到线程安全的铺垫了,我们直接对线程安全的懒汉形式进行分析,懒汉式的单例模式是在调用 getInstance() 方法的时候去判断单例的对象是不是为空,此时为了防止多线程并发的问题,我们需要进行同步锁的二次判空,关于下面代码中的 volatile 关键字我们在文章的最后进行解析;
    懒汉式既能在多线程环境中很好的工作,也能够保证在我们需要用到单例对象的时候创建对象,因此是我们用的最多的单例形式;
public class AppManager {
    // 创建一个静态的引用,但是不创建对象
    // 这里的 volatile 关键字在本文最后会进行分析
    private volatile static AppManager mInstance = null;
    // 私有化构造
    private AppManager() {}
    // 提供一个外部访问单例的方法
    public static AppManager getInstance() {
        // 第一重判断对象是不是为空,只要调用该方法就会判断
        if (mInstance == null) {
            // 第二重判断对象是不是为空,这里将当前类的 Class 对象
            // 作为同步锁,保证不同线程的执行顺序
            synchronized (AppManager.class) {
                if (mInstance == null) {
                    mInstance = new AppManager();
                }
            }
        }
        return mInstance;
    }
}
  1. 静态内部类形式
    该种方式是利用静态类只会加载一次的机制达到单例的效果,此种形式加载也能够达到懒加载的效果,静态内部类形式的单例模式是推荐使用的单例模式;
public class AppManager {
    // 创建一个静态内部类,该静态内部类持有一个单例类的静态成员变量
    private static class Holder {
        private static final AppManager INSTANCE = new AppManager();
    }
    // 私有化构造
    private AppManager() {}
    // 提供一个外部访问单例的方法
    public static AppManager getInstance() {
        return Holder.INSTANCE;
    }
}
  1. 容器式
    容器式的单例模式我们平常自己写的代码中并没有使用的很多,但是我们查看Android的源码的时候,经常能够看到它的身影,例如我们获取加载布局资源的 LayoutInflater 对象,实际上我们并不是通过 LayoutInflater 类本身来获取,而是通过系统的容器来获取 LayoutInflater 对象,容器式的核心思想是通过一个容器(例如: 'HashMap'),将我们需要的对象缓存起来,如果需要用到该对象的时候,我们只需要通过容器获取即可,因此此种设计模式对应类的构造大概率不是私有的;
  2. 枚举
    我们平常使用的枚举形式其实也是单例设计模式的一种,但是由于枚举会占用比较多的内存开销,因此不推荐使用枚举来实现单例模式;

AppManager Activity管理工具类

在项目开发的过程中,我们需要对打开的 Activity 对象进行有效的管理,例如我们收到一个推送需要立即显示一个 Dialog 弹窗就需要获取当前显示的 Activity 等,接下来我们用 懒汉模式(DCL) 实现一个 Activity 管理的工具类:

public class AppManager {

    private volatile static AppManager mInstance = null;

    private Stack mActivitys;

    private AppManager() {
        mActivitys = new Stack<>();
    }

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

    /**
     * 新增一个Activity
     *
     * @param activity
     */
    public void attachActivity(Activity activity) {
        if (activity != null) {
            mActivitys.add(activity);
        }
    }

    /**
     * 移除一个Activity对象
     *
     * @param activity
     */
    public void detachActivity(Activity activity) {
        if (activity != null && mActivitys.contains(activity)) {
            mActivitys.remove(activity);
            activity.finish();
            activity = null;
        }
    }

    /**
     * 结束栈顶的Activity
     */
    public void detachLastActivity() {
        if (mActivitys.isEmpty()) {
            return;
        }
        detachActivity(mActivitys.lastElement());
    }

    /**
     * 根据 Class 对象结束单个 Activity
     *
     * @param activityClass
     */
    public void detachActivity(Class activityClass) {
        for (int i = mActivitys.size() - 1; i >= 0; i--) {
            Activity activity = mActivitys.get(i);
            if (activity.getClass().getCanonicalName()
                    .equals(activityClass.getCanonicalName())) {
                detachActivity(activity);
                break;
            }
        }
    }

    /**
     * 根据 Class 对象结束一类 Activity
     *
     * @param activityClass
     */
    public void detachActivitys(Class activityClass) {
        for (int i = mActivitys.size() - 1; i >= 0; i--) {
            Activity activity = mActivitys.get(i);
            if (activity.getClass().getCanonicalName()
                    .equals(activityClass.getCanonicalName())) {
                detachActivity(activity);
            }
        }
    }

    /**
     * 移除所有的Activity
     */
    public void detachAllActivity() {
        for (int i = mActivitys.size() - 1; i >= 0; i--) {
            Activity activity = mActivitys.get(i);
            detachActivity(activity);
        }
    }

    /**
     * 获取栈顶的Activity
     *
     * @return
     */
    public Activity getLastActivity() {
        return mActivitys.lastElement();
    }

    /**
     * 获取指定类型的Activity
     *
     * @param activityClass
     * @return
     */
    public Activity getActivity(Class activityClass) {
        for (Activity activity : mActivitys) {
            if (activity.getClass().getName().equals(activityClass.getName())) {
                return activity;
            }
        }
        return null;
    }

    /**
     * 获取Activity栈的大小
     *
     * @return
     */
    public int getSize() {
        return mActivitys.size();
    }

}

完成AppManager 后我们只需要在应用的 Application 中调用registerActivityLifecycleCallbacks() 方法监听Activity 的创建和销毁即可;

registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        AppManager.getInstance().attachActivity(activity);
    }
    // ...
    @Override
    public void onActivityDestroyed(Activity activity) {
        AppManager.getInstance().detachActivity(activity);
    }
});

volatile 关键字

上面我们讲到 懒汉式 的时候,我们发现 mInstance 对象有个 volatile 关键字修饰,这个关键字可能很多朋友都听说过,这里我们简单的分析下其功能:

  1. 防止指令重排;

JVM为了优化执行效率,对需要执行的代码(方法中的代码)进行指令重排,其结果不会影响我们单线程中对方法执行的结果,但会打乱方法中没有关联语句的顺序,例如:

// 情况1,存在第二行代码在第一行代码前面执行的情况
int a = 0;
int b = 1;
// 情况2,此时 b 依赖于 a,第一行代码会在第二行代码之前执行
int a = 0;
int b = a;

上述代码中的第一种情况,在单线程中对代码的结果没有任何影响,但是如果是多线程的情况下就有可能出现问题,我们再来看一段伪代码:

int value = 0;
volatile boolean flag = false;
// 假设下面的代码在线程 a 中执行
value = 1;
flag = true;
// 假设下面的代码在线程 b 中执行
while(!flag){
    sleep();
}
System.out.println("value" = value);

假设 flag 变量没有被 volatile 修饰,上述代码出现指令重排的情况,线程 a 中的flag = true;优先执行了,那么线程 b 中的打印语句就有可能出现 value = 0 的情况,使用 volatile 关键字可以避免指令重排,保证线程 a 中 falg = true; 会在 value = 1; 代码后面执行;

  1. 被此关键字定义的变量能够保证线程的可见性;
    先来看下下面的这段代码:
public class VolatieTest {
     public static void main(String[] args) {
           ThreadDemo td = new ThreadDemo();
           new Thread(td).start();
           while(true) {
                if(td.getFlag()) {
                     System.out.println("----------------");
                     break;
                }
           }
           System.out.println("while out");
     }
}

class ThreadDemo implements Runnable {

    // 这里 的 flag 如果没有添加 volatile 关键字则 上述 的 main() 里面的while循环无法跳出
     private boolean flag = false;
     @Override
     public void run() {
           try {
                Thread.sleep(200);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }      
           flag = true;
           System.out.println("flag = " + flag);           
     }

     public boolean getFlag() {
           return flag;
     }
}

没有增加 volatile 关键字时:

没有增加 volatile 关键字

增加了 volatile 关键字时:

增加了 volatile 关键字

第一张图没有增加volatile 关键字, ThreadDemo 类里面的 flag 变量在子线程中更新了,在主线程中无法获取到这个更新,因此主线程一直卡在 while 循环里面,第二张图增加了 volatile 关键字,子线程中的 flag 变量一更新,主线程可以立刻知道 flag 这个变量有修改,因此跳出了 while 循环;
关于 volatile 这里只是简单的做了分析,想要了解更底层的原理的大家可以网上单独找找资料;

你可能感兴趣的:(单例模式)