zoukankan      html  css  js  c++  java
  • 单例模式

      单例是最简单的设计模式,没有之一,常见于缓存管理对象、数据库连接对象、帮助类对象等只需要唯一实例的地方。但是简单不代表用起来就真的容易,很多老手可能都不清楚真正该怎么用它,最常见的错误就是没有定义一个私有的无参构造器,见下面代码:

        private static final OsCache cache = new OsCache();
        
        /**
         * 单例
         */
        public static OsCache getInstance()
        {
            return cache;
        }

      在使用该OsCache对象时,我们通过getInstance方法能获取唯一的单例对象,这点是很明确的,但我们同样能new出一个OsCache对象,这时就不能叫单例了。因此正确的单例模式应该补一个构造器:

        private static final OsCache cache = new OsCache();
        
        /**
         * 私有无参构造器
         */
        private OsCache()
        {
        }
        
        /**
         * 单例
         */
        public static OsCache getInstance()
        {
            return cache;
        }

      这样如果别处用到该OsCache对象时,就无法通过new来实例化它了,直接编译报错。像上面这种模式有个形象的叫法:饿汉模式,因为我们一开始就new出来这个实例,表示我们很饿,需要马上吃了这个实例。为什么叫单例?因为所有用到OsCache对象的地方,其实都是在用这个实例。就好比目前我们人类一样,一来到这个世界,就只有我们自己,每个人都是唯一的,没有克隆人存在。既然有饿汉,就有不饿的,需要时才实例化出一个单例:

        private static OsCache cache = null;
        
        /**
         * 私有无参构造器
         */
        private OsCache()
        {
        }
        
        /**
         * 单例
         */
        public static OsCache getInstance()
        {
            if(cache == null)
            {
                cache = new OsCache();
            }
            return cache;
        }

      这里在获取单例之前,会先判断下单例是否已经生成,没有再去实例化,此种模式曰懒汉模式。懒汉虽懒,却有两个变种。一个是考虑到并发调用getInstance方法时,A线程看到cache实例是null,刚进去new实例的这一刻,B线程刚来到cache实例为null这里,那么接下来A把单例new出来后,B也会new出来另一个实例。这时单例就不叫单例了,因为有两个实例。怎么办?加锁:

        /**
         * 私有无参构造器
         */
        private OsCache()
        {
        }
        
        /**
         * 单例
         */
        public static OsCache getInstance()
        {
            synchronized (OsCache.class)
            {
                if (cache == null)
                {
                    cache = new OsCache();
                }
            }
            return cache;
        }

      加锁之后可堵住并发时new出一堆实例来这个漏洞,但如果并发获取该单例的情况存在,第一个获得锁的A线程将实例化对象,后面所有排队的线程都是冤大头,因为A已经帮他们new好了单例。所以getInstance方法的性能会随之下降。更好一点的写法是再套一层判断:

       private volatile static OsCache cache = null;
        
        /**
         * 私有无参构造器
         */
        private OsCache()
        {
        }
        
        /**
         * 单例
         */
        public static OsCache getInstance()
        {
            if (cache == null)
            {
                synchronized (OsCache.class)
                {
                    if (cache == null)
                    {
                        cache = new OsCache();
                    }
                }
            }
            return cache;
        }

      这样A线程领头取到单例后,后面还没排队的请求一看单例有就立马拿走,无需排队了。这种写法叫“双重检查锁”(double-checked loking),在锁住的门外问一声:单例在不在?在就领走了,不在才去门口排队,因为拿到钥匙开门时可能其他人已经把单例创造出来了,所以排到了进门还得再问一声:单例在不在,在就领走,不在new它出来。这里注意单例需要设置为volatile,让所有排队的人都能看到单例的状态,否则可能别人已经创造了一个单例,但我在第一个非空判断时还是看不到。这里涉及到volatile对多线程状态下的可见性问题。小小单例,细说起来还是有不少可以聊的。

      最后科普下,如果是一开始就是实例化出对象来,说明对该单例资源很饥渴,被称为“饿汉”,加了非空判断延迟加载的,则称为“饱汉”或者“懒汉”。

  • 相关阅读:
    JB的IDE可视化MongoDB、MySQL数据库信息
    爬取QQ音乐(讲解爬虫思路)
    selenium实现淘宝的商品爬取
    爬取中国福彩网并做可视化分析
    Django的学习进阶(二)———— name
    xpath获取一个标签下的多个同级标签
    selenium中动作链的使用
    Error: Cannot find module 'electron-prebuilt'
    error Invalid tag name "–save-dev": Tags may not have any characters that encodeURIComponent encodes
    TypeError:mainWindow.loadUrl is not a function
  • 原文地址:https://www.cnblogs.com/wuxun1997/p/8672514.html
Copyright © 2011-2022 走看看