• 设计模式单例模式


    单例模式的英文叫做singleton模式,我先说一下,单例模式是怎么回事,就是,在你的系统里,你要判断一下,如果有一些类,只需要一个实例就可以了,那就给那个类,做成单例的模式。

    实际上我应该说,这个单例模式也是一个,是个人就会的设计模式,因为我在面试的时候,基本上问别人设计模式这一块,基本上90%的人都会说,要不然,我说一下单例模式吧,然后,或者是他们一般会说,要不然,我说一下工厂模式吧,一般所以说,我到了后面就给他们说,说一个你在那个实际项目里面,这个实践过的,使用过的设计模式,结合你的业务需求,然后来讲。ok,然后那个,不允许讲单例模式和工厂模式,这两个模式除外,因为这两个模式实在是太简单了。

     ok,然后这个单例模式常见的一个场景,可能就是两类,第一类的话,就是,比如说 你自定义了一个什么什么框架,然后针对这个框架,然后,你自定义了一份xml格式的一个配置文件,对吧,然后你要那个读取这个配置文件,这个配置文件种的数据,读取到类中,然后这个类的实例,只要保存一份就可以。那么此时,可以使用这个单例模式,然后,将这个类做成它的这个实例,只能有一个,然后在这个实例中,保存了配置文件中的数据,ok,这是第一个场景啊。

    然后第二个场景的话,就是说是,类似于,我们之前给大家讲解的这个工厂模式,然后那个工厂模式里面,其实我们提到了有些这个工厂啊,是需要实例化对象的,因为要基于这个,实例化对象,然后来实现一些,比如这个继承啊,然后接口啊,实现等功能吧,对吧,然后,所以说,像那些工厂实例啊,可以做成这个单例的,就可以了。

    反正除此之外吧,其他情况下,你自己去判断,如果是一个类的这个实例,只需要保存一份,那就做成单例,所以说单例什么时候用,我相信只要智商正常的人,你自己在做这个系统的时候,只要你智商正常,一般来说都能判断出来,然后其实比较重要的是,这个单例怎么来实现,这个单例所谓的实现啊,它其实分成,大致分成两类啊,一种叫做饱汉模式,其实我之前给大家用的就是,不对啊,这个第一种应该叫做饿汉模式。

     什么叫做饿汉模式啊,就很简单,其实就是说是,我们之前在写那个工厂的时候啊,其实就是用这个,所谓的这个饿汉模式,饿汉模式里面,就比如说,我们这有一个类啊,

     对吧,你看,像这个singleton,

    然后像这个singleton的话呢,就是说是,你看,我这里都给你把代码,给大家写出来来了,在这个里面,然后你搞一个变量,叫做private static final,这个变量,得给它搞成静态的,然后final是不可变的,然后直接就把它的这个,类的实例给它创建出来了,然后的话呢,把它的这个singleton,它的这个构造函数给它弄成private啊,然后这边,其实就是第一步么,或者我们在这个代码里面给大家写一下吧,还是。

    先给大家写一个,所谓的这个饿汉模式,叫做HungrySingletonPatternDemo,就是这个饿汉模式,其实就是这样,public static class Singleton,然后这个Singleton的话呢,在这里面,你看这个第一步啊,或者我们在这先把它写出来吧,就是private static final Singleton instance = new Singleton(),然后这个,直接就是,将这个类的实例在这里创建出来,然后赋予这个static final修饰的变量,这个static的意义是说,就是一个类的这个静态变量,这个final是说,这个变量的引用第一次初始化赋予之后,就再也不能够修改这个引用了,这些都是java中最基础的,我就不想多说了,

    好,然后的话呢, 第二步其实就是将它的这个 private Singleton(){},把它的构造函数给搞成,私有的,第二步就是将构造函数搞成这个private,私有的,此时除了这个类自己本身,其他任何人都不能创建它的这个实例对象,ok,这个是第二步。

    然后第3步的话,是给个方法叫做,public static Singleton getInstance(){return instance;},这个其实就是所谓的第3步,然后,给一个static,静态方法,然后返回自己唯一的,内部创建的一个实例,就是在它这个类初始化的时候,它这一行代码就会执行,然后类一初始化,直接就把它自己的实例给创建出来了,对吧,ok,然后的话,我们来用一下这个类,这个就很简单了,Singleton,哎,我们其实可以给它一个方法 啊,public void execute(){ System.out.println("单例类的方法");},这样吧,Singleton singleton,然后 = Singleton.getInstance();,然后,singleton.execute();,就这样呗,对吧,来看一下,ok,单例类的方法。

    其实为什么叫饿汉模式啊,因为,这个类,你可以认为这个类它很饿,很饿就是说,它说,我好饿啊,好着急啊,所以说,它因为很饿,它着急忙慌的,在自己这个类初始化的时候,直接就是把自己的这个对象实例,给它初始化出来了,所以给它叫做饿汉模式。

    但是,你会发现,这种模式下,那么这个Singleton这个类的实例,全局就只有一个,就只有它,因为外面不能再对它去创建实例了,比如说你要是这样子,Singleton,你自己看看,肯定报错啊。啊,因为它是这个内部类,所以是这样子啊,但是如果你把它给放到外面去,就是这样子。给大家看一下啊。比如说把它给放到这来。啊行,那我就不给大家去弄了,这个,那我就不给大家去演示了,这个是因为,它是它的一个内部类,所以它还可以去创建它,但是正常来说的话,哎,这个应该是,我还可以给大家去演示出来,就是Singleton这个类,比如说,我这边把它给注掉,大家看啊,啊,这样就好了,你看比如说,我如果把这个Singleton放到外面去的话,你在这里去,如果你去newSingleton的这个构造函数,去创建它的实例,是不行的,你看它说,The constructor Singleton() is not visible,就是说它的这个,Singleton的 这个构造函数是私有的啊,

    ok,我们把它给删了,给它放一块,然后给大家来演示一下。

    啊,好,这个就是所谓的 饿汉模式。然后的话呢,再给大家来搞一个,那个叫做,线程不安全的啊,UnsafeFullSingletonPatternDemo,就是线程不安全的饱汉模式,这是线程不安全的饱汉模式,首先什么叫饱汉模式,饱汉模式,它的意思就是说,哎,我这,肚子好撑啊,对吧,然后我实在是不想在上来,一开始的时候,然后这,有个private static Singleton instance,我不想一开始上来就创建我的这个实例,因为我太撑了,我是这个饱汉啊,我并不饥渴,所以说我不想,在这个类初始话的时候,上来就在这,创建我的这个对象实例,然后我在,我希望是,你看public static Singleton getInstance(){},当然这边是要给个private Singleton(){},然后我希望的是说,如果有人,要来获取的我的实例,我在这给它singleton,这边来给它判断一下啊,if(instance == null){instance = new Singleton} return instance,如果它是null的话,new Singleton,然后,给它返回instance,就这样子啊。

    啊,也就是说是,这是一个单例类,对吧,然后它说,我肚子好撑,我是不好看,然后,我不想上来就给它,初始化这个实例,然后我提供一个方法。这个方法里面说,你来调用的时候,你第一次来调用,如果我发现说,这个实例是null,然后我就给它创建好实例,然后给它返回,那么后面的话呢,大家再有请求过来,直接去用,我之前创建好的这个实例就可以了啊,然后,这个 mainclass不用写了,因为没有什么好演示的,因为太简单了。

    但是这个,为什么说是,线程不安全,线程不安全的话呢,它的一个核心的一个意思,就是线程不安全,主要是因为说,你可以想象一下啊,它之所以称之为线程不安全,主要的问题出在这,问题出在,假设有两个线程过来,对吧。然后,第一个线程,判断发现说,这个instance == null,然后这个代码就进入到了下面去,那个第一个线程跑到了这来,ok,稍等,但是此时第一个线程,还没有执行下面的那行代码,那首先各位统信,你们有没有最基本的一个,计算机的这个基础啊。这个线程,它是并发着去执行的啊,这个,所谓的这个线程的基础,就是有一个前提,就是线程是并发着执行的,就是你的系统的,你可以认为系统的这个cpu啊,它是,先执行一会,线程1,然后停止执行线程1,然后,切换过去执行线程2,对吧,然后那个执行线程2,一会之后,再停止执行线程2,然后,回来继续执行线程1,所以,它是这样一个概念。

     这个东西,太基础了,我就不多说什么了,然后,第一个线程它先跑过来,它说,这是null啊,然后第一个线程的代码执行到了这里,cpu给它停了,切换到了第二个线程,

     然后,第二个这个线程执行到这,对吧,那个发现此时instance,还是null啊,对不对,因为第一个线程还没有去执行这行代码呢,所以第二个线程,就说这个instance还是null,那么就没什么问题了,然后,继续往下走对吧,ok,此时第二个线程的代码也执行到了这,对吧,好,然后那个,这个时候,cpu切换回线程1啊, cpu去执行,或者说那个,执行线程1的那个代码,然后线程1切回来执行这个代码,线程1会创建一个实例出来,然后线程1,就走了,它可以拿到这个实例,但是切换到这个线程2去执行,这时候啊,这个,这个线程2 的这个代码啊,已经执行到这来了,就是线程2的代码已经在这个括号里面了对吧,那么此时,优惠执行下面的那个代码,就是会再次创建一个实例,然后之前线程1创建的那个实例,就会被垃圾回收,因为就没有人引用了,就已经被废弃掉了,对不对,其实就是因为会有这样的一个线程不安全出去的情况,可能会去导致,这一个实例创建很多次啊,所以它叫做线程不安全的,线程不安全就是多线程并发去访问一个共享资源,你看这就是一个共享资源的时候,因为线程来回来去的切换,可能就会导致,这里就会有问题,ok,好,这个叫做线程不安全。

    那么如果要做成线程安全的话啊,有一个那个SafeFullSingletonPatternDemo,就是说是,常规性的一个方法,应该怎么来做呢,public static class Singleton,然后它说,我是这个饱汉吧,private static Singleton instance; ,private Singleton(){},public static Singleton getInstance(){},这里面,它先判断一下,if(instance == null),如果它是null的话,ok,synchronized,加锁,ok,你看啊,加的这个锁,因为它是一个静态方法,所以它加的锁,只能加在它的.clas上面啊,然后这个里面,还得再来判断一下,如果是null的话,然后那个instance = new Singleton(),啊,就是这样的一个逻辑,这个东西,叫做double check,好,这个是什么意思,大家可以再来想一下啊,那如果那个线程1,和线程2,都执行到了,这一步,if(instance == null),然后此时线程1,判断发现,还是这个null,对吧,那么线程1,就会进来,此时线程1,停止,切换到线程2,然后那个,切换到了线程2了以后,线程2,这个时候还在外面,线程2此时,判断发现,这个instance也是等于null啊,那么也会进去,对吧,然后此时线程2也会进来,此时切换到线程1,然后那个,线程1,然后发现了,这里需要枷锁,然后线程1呢,那它就在这里加锁,然后它呢,获取到了这个锁,它加锁成功了,对吧,那么线程1呢就进来了,对吧,然后此时切换到线程2,线程2,这时候在这嘛,对吧,然后线程2过来,然后这个线程2,发现说哎,我也想要在这里加锁啊,结果尝试加锁,发现说,这个锁被人加了啊,所以说线程2,挂起等待,别人释放锁,对吧,线程2就卡这了,然后那个,此时再切换回来,切换回这个线程1,线程1此时在这里,再次判断这个instance,它还是这个null,然后这个,线程1,就会进来,创建一个实例,对吧,然后这个线程1,创建完了以后,就啪嚓一下返回,线程1就执行完了,然后那个这边,然后那个出来以后,线程1,就释放锁了,线程1释放锁了以后呢,然后那个,此时切换回这个线程2,线程2发现锁被释放,然后这个,在这里加锁,对不对,然后,那个线程2就进来了,所以说这边,为什么叫做doublecheck啊,就是说为什么锁前也要加一个if(instance==null),下面为什么又要加一个if(instance==null),因为如果这里没有instance,是null的这个判断,那么线程2,就会再次创建一个实例,但是这里是双重检查,然后这个线程2,又判断了一下,哎,这个instance,是否为null呢,否,instance这个时候,因为之前已经被线程1给创建出来了,所以它已经不是null了,否,然后线程2就直接,if就没有执行,啪,跳出来了,线程2跳出来了,然后,直接获取一个instance返回了,这个,这个instance,就是之前线程1创建的这个实例,ok。

    好, 那么所以说这个东西,就是说 用doublecheck去确保线程的这个安全,但是的话呢,要给大家说一点啊,就是像,这个模式其实它还是,不是完美的,因为不同的JVM的编译器的问题,可能导致说,这个情况下,还是线程不安全的,具体的我不在这讲,因为涉及到复杂的jvm,内部的这个原理,然后但是,我只告诉大家一点,doublecheck,双重检查它,也也不是说完全线程安全的,那如果要做到彻底的线程安全,要怎么做呢,要做成那个叫做,InnerClassFullSingletonPatternDemo,就是基于内部类来实现这个Singleton啊,public static class Singleton,然后这边是,public static class InnerHolder,首先它这里面有一个 private Singleton(){}对吧,然后这边是public static final Singleton instance = new Singleton();,然后在这个外面,public static Singleton getInstance(){ return InnerHolder.instance;}

    ok,这个是,完全线程安全的,而且它可以做到饱汉,这个可以做到饱汉模式,就是上来,就是它的实例上来是没有初始化的,然后,就被放在了它的一个 内部类里面,这个内部类,只要没有被使用,就不会初始化,那么那个Singleton的这个实例,就不会创建,就这一行就不会执行,那么在第一次有人调用那个getInstance这个方法的时候,第一次有人调用它,那么我们就返回那个内部类里面的这个实例,这个时候,你第一次使用它,那么这个,然后内部类会初始化,然后创建一个Singleton的这个实例,额,第一次有人调的时候,然后这个java能确保的一点是,就是类静态初始化的这个时候,类静态初始化的这个过程,一定只会执行一次,也就是说这个内部类,它一定只会初始化一次,在第一次有人调用这个方法的时候,第一次执行到这的时候,然后它会初始化,那只有它唯一的一次,初始化的时候,然后才会创建Singleton这个实例,这个东西能够保证,首先,它的这个实例,不是上来就创建的,它可以保证一个饱汉的模式,然后当你第一次需要的时候,第一次调用这个方法的时候,它初始化,然后才会创建出来它,而且它一定是线程安全的,因为这个类的,静态初始化的这个过程,jdk,就说这个jvm给你保证,它一定只给你保证一次,一定不会说因为有多线程,这段代码执行多次,一定不会啊,不可能的一件事情。

    好,所以说这个才是说,这个才是我们实际开发过程中,最最常用的这个,单例模式,就是用这个内部类的方式来实现啊,这个是最最常用的,而且是最最保险的,最最安全的,最最稳妥的啊。那么这个单例类讲了4个基本上到这就差不多了啊。然后,在咱们项目里的化,其实还是有很多地方可以去实践的,比如说一些,其实这个比较好的一个实践,就是说,在咱们这个项目中对一些工厂的这个实例,就是说结合一些工厂模式来做了,就是说对一些工厂的实例可以做成是单例的,这个都没有问题,ok,好,那么本讲内容差不多到这,就可以结束了。

    end

  • 相关阅读:
    全干货!百度AI快车道艾尼专场成都站开启报名
    很用心的为你写了 9 道 MySQL 面试题
    「00后缩写黑话器」登上GitHub热榜,中年网民终于能看懂年轻人的awsl
    GitHub 上有个沙雕开发者,做了款斗图工具后火了...
    专访轮子哥:我在微软「造轮子」,一不小心成了知乎大V
    Mysql 查看varchar和char类型表的列长度
    用git统计代码提交行数 规格严格
    信息安全配置核查系统设计与实现 规格严格
    堡垒机(运维审计系统)的基本原理与部署方式[转] 规格严格
    PostgreSQL中search_path的设置 规格严格
  • 原文地址:https://www.cnblogs.com/HarryVan/p/16754073.html
Copyright © 2020-2023  润新知