美文网首页
单例模式

单例模式

作者: hewenyu | 来源:发表于2019-02-15 11:41 被阅读0次

    介绍

    单例设计模式(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<Activity> 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 这里只是简单的做了分析,想要了解更底层的原理的大家可以网上单独找找资料;

    相关文章

      网友评论

          本文标题:单例模式

          本文链接:https://www.haomeiwen.com/subject/wpspeqtx.html