00:00
Hello,大家好,我是上硅谷家艺学科的讲师柴林燕,今天呢,给大家分享一道面试题,这是一道高频面试题,那么那你写一个singleton的实例。那么下面我们来分析一下这道题目。首先呢,我们要看single to。Single to,那我们看到这个single的时候就会想到啊单身单个是吧?那么single to呢,在我们Java当中呢,就是指单利设计模式,它是软件开发当中最长的设计模式之一了,这里提到了啊单力,那什么是单力呢?单就是单个的唯一的力,那其实就是我们的实例对象了。那么单位设计模式就是指啊这样的一种类了,在整个系统当中,这个类呢,只能有一个实例对象可以被我们获取和使用,这种代码结构呢,那么我们称为单例设计模式。那么在我们软件开发当中还是会遇到这样的一些类型的,比如在Jie核心内库当中就有这样的一个类型,叫runtime类,它代表着郊外虚拟机的运行环境,整个的虚拟机运行环境,它把它这个表示成一个对象,那么其他的当中也会遇到类似这样的。
01:16
那么我们怎么自己如果需要的话,怎么去设计这样的这个单例类呢?我们给大家呢,分析了几个要点,大家需要去注意的地方,第一个呢。这个类呢,只能有一个实例。那么要想了,我怎么保证他只有一个实力?那么这个实例。肯定就不能随便去创建了,我们得需要在这个类型当中去创建,不然如果你在外面随便创建的话,那我就不能保证啊,就是我们得叫什么呢?就是用我们得约束啊,就是使用者啊,不能去随意创建这个,那我们就自行创建,自行创建完以后,我得向整个系统提供这个实例,不然人家拿不到这个实例就没有意义了,那么分别怎么实现这几个点呢?
02:01
外面不能随意的创建,只能有一个,那因此我们得约束,怎么约束它呢?构造起私有化,那么外面就不能随意的去创染对象了。那么我们这个自行创建,创建完这个实例以后呢,我们得用东西保存起来,所以需要用一个静态变量来保存。静态变量来保存这个唯一的实例,当然这个类型肯定还是这个单列类的类型,单类的啊这个静态变量,第三个呢,我们得向外的提供这个方式,提供这个实例的方式,那么怎么去提供呢?第一种就直接暴露出来。就把这个静态变量公开public,第二种呢,我们可以啊,用静态变量的get方法,让他去得到这个实例对象,那么我们来写一写代码啊。那么因为啊,这个实现的方式有很多种,那么我们呢,给大家呢,这个归纳成两大类,第一类是饿汉式,第一类是第二类是懒汉式,那么这两种什么区别呢?主要的区别在于饿。
03:07
他就很着急对吧,急不可耐是吧,饥不择食是听到是这种成语,所以呢,他就很着急,他就直接创了对象,不管你是不去使用这个对象。他都要去把你传递出来,很着急,第二呢,懒汉是懒人嘛,那就是不到万不得已他不会去做,所以呢,我们要延迟穿对象,那分别怎么去写呢?我们用多种形式给大家来展示一下啊。好,因为我们要写好几种形式,所以我这里呢,就是分别命名为single to1。和234啊这样的顺序来排列好第一个我们先写第一种形式二函式的,第一种直接是例化的函式,我们来看看啊,它是怎么实现的,我们刚才提到了几个要点,还能记下来吧。第一点是我们构造起私有化,因为不能够让外面的去创建我的实力,所以呢,我们来构造起私有化第二步。
04:14
我们得在内部创建,所以我们自行创建,并且呢保存,那它保存呢,得用什么保存呢?静态变量什么类型的,当然你要保存它的实例对象肯定是这个统一这种实例的是吧?那么变量名变叫instant,我们只创建,那我们就是single啊to的一很简单标用勾导体就创建出来。第三个我们有一个要求是是外向外提供这个实力,那么怎么去让外面得到呢?因为它是静态的,其实可以通过类名直接去访问它,那么因此我们呢,可以考虑这样子他。好,这里呢,为了我们呢,就是强化或者叫强调,这是一个单例。
05:08
我们可以用一个关键词修饰一下final什么意思呢?我们在这个static这个地方加一个final,这样的话,这个它就不能去更改了,那就是这一个实例。那么这样子的话,我们呢,Final修饰的一般叫常量了,因此我们习惯上会把它命名为大写,就表示常量的意思,好第一个我们来写的这个二函式,二函式对吧。的形式第一种,那么大家看到的就是直接创建实力对象,有可能我不需要,就不管你,不管你是否需要这个对象,还有同学说我这个当中怎么会不需要这个对象呢?哎,比如说我这下面有静态方法啊,里面有一个静态方法static,那么如果你是调用这个方法的。
06:09
你要用这个类似利用这个方法的,那么你其实根本不需要这个对象,你通过类名直接调用这个方法就可以,因此此时就不需要上用,但是我们如果这么写的话,因为它是静态的常量,因此在加载并初始化这个类的时候,这个对象是会穿越出来的,因此就不管你需不需要我都穿越出来的啊,那么这里呢,我们就涉及到啊,那么这里呢,就最简洁的写到这儿了啊。就是你面试的时候就下面那心态话就不需要写了。直接创建实例对象,不管你是不需要这个对象都会创建啊,都会创建这第一种,那么我们GDP1.5之后,对这种形式呢,有一个更间接的方式就是枚取。他跟我们刚才这种就是一样的啊,只不过就是换了一种写法。那么我们呢?来来看一下啊,枚举你直接写single to第二个。
07:08
嗯。那么枚举它其实代表的是这个类型对象是有限的几个,对,那我们说这个枚举类型,枚举类型那就是表示该类型的这个对象啊,该类型对象是有限的几个,那有限的几个,那我们可以我们可以限定为一个,那么就成了单例了,那它怎么写的呢?它在这个地方呢,第一行是要写产量对象列表,这就是单列了。他跟我们刚才的第一种写法是几乎是一样的,只不过呢啊,我们这里呢是自己来用了一下,它这里呢,就是他帮我们用了一下,但代码最终其实是一样的,那么它的枚举类型的构造起全部都是私有化,那么你看这个代码跟这个比的话就简洁了很多,它俩是效果是一样的。
08:07
那我们怎么去获取这个对象对吧,这种那么他俩都是直接暴露的,我们来啊获取一下,测试一下test single啊这个一我们测试一下获取它的对象。那么这个当中single to s等于single to,注意它是public,所以说我们可以而且是静态的。静态的,那么我们呢,看着啊,来,我来。对比的类名,一个静态的,又是常量,所以通过类名直接去访问,我们打赢了这个效果,S运行。他就会得到了我们的这样的一个对象,因为他没有重写图形方法,所以显示的是类名,以及他的哈西扣的嘛。
09:03
那么我们这个当中啊,枚举类型的获取方式呢,跟刚才一样,但是可能有同学呢,可能会发现啊。第二个枚举类型啊,跟刚才的获取方式呢,是一样的啊,Single它不是new的了,对吧,直接in看你看一模一样,只不过它打印出来的可能不太一样啊,因为它枚举里面呢,就帮我们这个重写的托进方法,它返回的是产量对象的一个名字,就是这个他的这个名字,产量对象的名字。这就是我们的枚举类型。好了,那么这两种类型就是基本上是一样的啊,就是1.5之前,1.5之后的分别,那么第三种它提到了一种叫做静态代码块的函式这种形式啊。是什么样子的,那么我们来写一下啊,这种形式。
10:05
好,我们写第三种,Single。Three。它呢,哎,是我们第一种跟第二种的一个复杂感。怎么个复杂版呢?首先第一步还是我们的勾到器私有化没变,先写上,然后呢,我们这个一样啊,Study final single to啊,那么instance。那么这里呢,我们啊,除了在这个地方直接扭,我们可以在这个里面简单大码块里面去给它扭,这个对象等于六个对吧,那今天到哪块吧。当然。这种如果你是用这种。无参构造直接用这个对象,那么你这个跟刚才比,你对比一下这个复杂度啊,对吧?那么我们会发现这种是不是更简洁呀,它执行的效果一模一样,一模一样的,也是在类加载和初始化的时候,然后呢,这个呃,静态的常量就直接去创建出来啊,所以这个静代表块也是一样的,是随着类的加载和初始化呢,直接就执行了啊,它这个当中是一样的效果,那么我们什么情况会用到他这种情况呢?对吧?那么只有一种情况可能会用到什么情况呢?我给来举个例子啊,比如说我们有这个属性需要去出手啊,也就是我这个当中可能是这样子的。
11:40
那么这个里面这个常量值,当然第一种,如果你是直接给他一个常量的话,对吧?哎,也就是说你给他一个常量,那么没问题,这个的话其实也可以直接呢,等于在后面是吧,没必要用这个,但问题是可能这个值啊,它不在这个当中,不是一个常量的字符串,它在哪里呢?可能在这个地方,比如说给大家看一下在这个文件里面。
12:07
我这个音啊,是这个用一个配置文件来把它的值呢给存起来的。那么配置文件呢,就更灵活了,我们可以在这个后期的时候更改,不用去修改源代码,改配置文件就行了,对吧?那么后面我们很多的这个框架啊或等等的都是通过这种配置文件的方式来配置信息的,那么也就这个值我要从文件里面去读取,那么如果你截有的话,就很难去做了,那么怎么办呢?所以呢,我们这个当中啊,就需要比如说先初始化我们的这样的一个数据,从文件去初始化,那我们因为这里是一个特殊的文件叫pro的文件,就是属性配置文件,好,那我们这里面呢,用它的特独特的加载方式,好,因为这个当中它是我这个文件啊,是放在src下。我们最终编译是在内路径下面,对吧?诶,就是我们的并目录里面,那么这个时候呢,我们可以用内加载器啊去加载,那么我们看一下啊,这个pro.load load的加载用我们的类加载器,当前这个类也需要类加载器,所以我们可以得到它类加载器对象,怎么得到呢?class.get class load。
13:20
有内其对象就可以加载内路径下的资源了,Get resource,那么这个资源文件名我这里呢叫single,点这大呢这个文件啊,注意写一下路径,不要写错了,文件名从这个地方,文件名跟这个文件名是对应上的。要注意它的位置是在src下面才能用内加载器去加载啊好,那么现在呢,我们这个方法呢,需要用异常,我们呢这里呢把它呢处理一下异常啊,异常呢,我在这里呢就不catch处理了,而是把它再抛出去吧,New slowne一个变成运行时的一个异常。
14:00
这样呢,我们外面的就如果啊,就来文件失败,就创越这个对象失败,他就知道了,好,我们这个当中info怎么办呀,数据呢,就已经被加载到这个property对象里面了,那P里面有一个get property方法把这个key呢已经传给他,这样的话我们呢就哎完成了这样的过程。我们来试一下,来给大家这个增加一些方法,比如说我们的这个info,增加1OTHER set,再增加to string方法好,我们来演示一下啊,看一看它有没有这个创建好这个实际对象,并且呢,有没有把这个音符呢读过来,我们来测试一下啊。编写一个测试类test single的to的three出方法,好,我们来对比一下啊,我们拉过来代码要调用这啊获取它,那么怎么获取呢?它是public sta,那么因此呢,我们跟刚才是一样的,S等于它点啊访问方式跟第一种第二种一模一样,那我们打一下这个S运行。
15:13
信息艾特就加载进来,那么这个当中啊,我们只不过是从这个地方的new改成了在这去new。那么它适合于像这种比较复杂的,可能需要读一堆初始化的数据,可能才能把这个对象完成创建好,那么这种情况呢,我们就把它叫这个。静态代码块的啊,但它最终的要点还是没变化,你看还是这几个构造起私有化,然后用一个类变量静态变量存储起来,然后在这个内的内部对吧?哎,我们这个去把这个东西用对象的给你用好了,那外面我这里还是直接怕别的啊,那么在外面的这就过去,所以这个当中啊,我们恶寒是三种。就给大家写完了,那么这里它是恶汉时是不存在现场安全问题的,为什么呢?因为我们的这个Java的这个类加载的机制啊,就类加载器,它这个设计呢,就是可以啊这个避免现场安全问题,就是我们所有的这三种形式我们看一遍啊,第一个呢,是在这个我们来看一下,第一种是在这个当中去谬的,那么也就是说我是在single to1这个类加载和初始化的时候,直接用这个对象。
16:30
然后呢,Single two啊,那么也是一样的词瑞也是一样,它虽然在静态板块当中,但是经常板块跟这个的这个呃初始化最终会合成一个类初始化的方法,叫做c liit方法,那么这个是一口气完成的,并且是现场安全的这样的一个呃类初始化方法。那么这保证现场安全问题?那么他们三个共同的特点就是在类初始化的时候。直接谬的对象也就是我们这样呢,在什么的时候呢,类初始化是。
17:06
直接创建识别对象,不管你是否需要这个视频,接下来是不是需要,他都会去创建。这就是恶汉市,那么我们有的时候可能不需要啊这个对象啊,因此呢,就有人提出了,我们觉得恶汉市呢,太着急了是吧,可能我不需要这个对象,那么有人提出来就用懒汉式,那我们来写一下懒汉式。这个single的to for。懒汉式怎么做呢?好,我们的要点不变啊,第一步还是勾到起词有化的,所以呢,我们第一步来勾到器的私有化,第二步我们呢,用在类当中啊,这个用一个静态变量吧,用一个静态变量保存保存这个唯一的实例。
18:05
那么我们呢,这个static static好,然后呢,我们呢,好,现在的问题呢,在于我们要延迟去创建好懒还是要延迟创建这个实例对象,实施对象。因此,我不是在这里巡拗,也不是在金山宝里面扭。我们什么时候用呢?也就第三个,我们要提供一个这个静态方法来获取这个实例对象,因此我需要这样的一个public static。然后呢,反问这类型还是这个类型,我方法名就叫get instance吧,对吧,那么我们呢,哎,这个时候当你去调用这个方法的,就说明你要拿这个对象,你不调用这个方法就不拿这个对象,那么当你来调用这个方法的时候,我来把你用,因此这个地方就不能再把它弄成public了,因为public我可以通过类名直接调用,有可能它是空的,没初始化,所以说我们这个里面呢,要把它私有化,Private。
19:16
那么在这个当中怎么去用呢?我们判断一下instance等于空,我们呢,再来把它创建一下,等于new的啊这个类型你没创建过,我帮你创建一下,你创建过了我就不创建了,直接返回,那么因为它是静态的,是一个共享的这种效果,那因此的话,你创建过一次之后就不用再创建了。但是如果现在写到这一步的话。我们呢,这个。在单线情况下是没问题的,我们来演示一下啊,测试一下test single for,什么叫单线程情况下呢?比如说我们现在只一个主线程,我们来获取一下它的实例对象,怎么获取了呢?这回呢,好看着啊,类型S1等于它不是直接访问它了,因为我们private,如果public就有问题了,有可能我得到是not,所以呢,我们呢,用这个get instance方法,当你调用它的时候,就说明你拿这个对象了,那么我们呢,可以调用两边对吧,那么拿到了是同一个对象呢,我们来。
20:28
判断一下,如果他俩的地址相同对吧,获得我们可以显示一下他们的啊,这个信息打印的哈西库的值它会显示出来,如果哈西扣的值是吧,一样的啊,那么就是可能性就一样的可能性很很大了,但是我们这里用地址相等就确定它是一个啊。这个只是辅助我们理解一下啊这个项目。好了,那么这个是单向情况下没问题,对吧,你看我们这个处主要是看这个处啊,这个呢,就是辅助我们来看一看他们的信息啊,那么我们这个里面有可能会有现场安全问题,什么意思呢。
21:06
好,我们来给大家演示一下啊,现场安全问题。比如说我有两个线程来获取这个对象,那么下面呢,我们来写一个线程啊。要拿到什么对象呢?拿到他的对象等于啊你我就用匿名内部类的方式。这里面呢是个接口,那么因此呢,我们要把这个重写一下它的抽象方法,它里面呢作用干嘛呢,我就是为了拿到他对象,没有写别的东西。这个线程体就是这个号方法里面就是为了拿到的对象来着,那么我们启动一下这个线程吧。It service are yes等于are。我们呢,创建一个线程池吧,点是吧,创建一个两个啊两个线程的这样的一个线程池啊,然后呢,我们的es.submit把这个任务啊提交不上去,把什么任务呢?对吧,把这个C啊获取的任务,那么它会返回一个啊我们的这样的一个future对象好两个啊,我提交两边,那他就获取两个对象。
22:28
那么我们得到,因为它这个是有返回值类型的啊,那我们来看一下f1.getget的就是我们的要拿到的啊,这里呢有抛出异常,就是我们这个钙的方法抛出异常,所以我就直接lo出去了啊好,那么下面呢,就是得到这个S1,然后再来一个S2 S2这两个future里面呢,就相当于他执行了两遍,就两个线程的任务啊,那么执行两遍以后呢,我们同样的可以打印一下这个信息,最后呢,我把这个服务关一下S刷的框,我们来试一试,如果现在我们运行的可能大家看起来哎还是一样啊对吧?那么为了我们的这个呃效果,我在这里呢,故意加一句代码,什么代码呢,现成的休眠。
23:23
好,我们呢,这个让他休眠100毫秒啊,这是毫秒单位,好,那我们这里呢,就是它有异常速差开这一下,那么这个呢,就使得我们呢,哎来看看啊,多线程的效果呢,会更明显一点,运行一下。讲它有false对吧,那么我们这个当中,它是为什么会产生这个false,不是同一个对象,他俩的地址不是同一个,为什么会这种情况呢?原因是这样的,分析一下,第一个线程过来了,判断完是空,但他进来以后呢,因为遇到了这一代码,这一代码会导致这个线程呢,会被阻塞,阻塞完以后呢,也就是说CPU呢,就让出来了,那么另外一个线程对吧?就另外一个线程呢,它就会进来,他一判断,因为它还没扭,第一个线状一还没扭,所以他一判断还是空的,他也进来,他进来以后呢,也遇到了S。
24:15
好了,这个时候线轴一对吧,哎,休眠完了它就扭出来,但线虫二刚才已经到这一步了呀,已经到这一步了,那么我们等他休眠完以后呢,接着又用了一个,所以就导致了用了两个这种情况,那么我们这个呢,代码是有可能啊,就算没这一代码也有可能是在这一当中现成啊这个CPU的时间到了,然后呢,被切换到另外一些当中,所以我们任意这代码之间都可能被切换,所以说这个里面啊,我们是存在线状安全问题,刚来给大家看,确实有这个问题,他获取。当然这有概率的啊,不是说一定会,你看一会儿出一会儿放,是有概率问题的。那么因此呢,我们这个有线程安全问题,第一种它适合于单线程这种写法。
25:06
那么我们能不能去纠正一下,那么我们就要线程安全问题呢,可以用我们的同步去解决,怎么加呢?好,我为了这个的话,把这个再写一遍啊,复制一个五五,在刚才四的基础上,我呢把这个代码来修正一下,怎么同步呢?对吧,我们之前学过这个single n。把这个。代码。放到我们的。同步块里面这个时候需要选一个锁对象,锁对象的,那么我们选择当前类的class是一个锁对象,任一类型的对象都可以当所对象的啊,那么只要能够把这几个线程是吧?哎,这个都监视出就可以了,作为这个多个线程的监视器嘛,那当前类的这个就可以了,那么这个时候我们呢,看看啊,还是用我们刚才的那放代码,那么这时候呢,我们为了啊这个节省时间,我复制一下,但是呢,里面的类型换一换啊,换成什么类型呢?换成我们的这个这个类型啊,因此我们在这个当中把这个呢,改成我们的好复制一下,所有关于四的呢,改成。
26:19
对吧,好了,那么来试一下啊,我们这个呢,已经可以保证现场的一个安全问题处,那么比如我把这时间加大一点,有朋学说是不是因为这概率的问题,我们加大一点,对吧?疫我们再来运行。孩子的病,不管你怎么运行,它都是安全的。它都是安全的啊,那么这个是处,但是这个呢,还不是我们最优版。因为它这个不管我们什么时候来多个线程来获取它,对吧,可能前面两个线程我们来这个进来以后,我们可能需要这个,但后面的些能我们已经如果已经用过了,其实没必要再等待另外一些场那个锁的问题,那么我们为了优化这个性能是吧,我们可以再改装一下这个,这个就是完全为了性能,为了的效率是吧来考虑,其实安全问题已经解决了,刚才但是呢,我希望,哎就最开始的时候,可能最初大家第一次每个人第一次去抢这个的时候,对吧,获取这个对象的时候,我们呢,哎要互相之间的要锁一锁是吧,要看一下,但如果后面就是你已经不是空了,已经有人用过了,那就没必要这个锁的问题了,你判断这个不等于空,那么直接反拿来就行了,没必要再进这个,怎么这样呢,效率更高一点。
27:44
好,这个就是我们的现场安全版的。现场安全版的这样的一个懒汉式现场安全版。那么这个呢,代码呢,这么去写啊,还是挺复杂的,看起来那么我们呢,有这个另外一种写法,既能保持延迟加载,还能够保证行程安全,怎么做呢?下面来给大家写另外一个版本,Single to six。
28:12
怎么做啊,首先我们第一步还是构造器私有化,第二步还是由一个私有的静态变量static,然后呢,Instance。但是这个呢,我不存在这个里面,我存哪儿呢。注意啊,我要挪一下位置,存在这样的一个,那里面private sta,注意啊class,我们呢,给它取一个叫内,我把这个呢,我这来了。然后在这个里面呢,我们去拗它,拗它注意啊,在这个地方去扭。
29:01
同样你也可以跟刚才是吧强调,如果一个你也可以加final,加final以后呢,我们也很大写,那么这个我们怎么拿到它呢?哎,同样的道理,Public对吧,Sta。同样的我们呢,用这样的一个get方法,Get instance return in the,第二,首先它是在什么是创建对象的呢?这里要注意一下,在内部内被加载和初始化时。才创建这个对象,对,才创建的对象。那么这个时候我们什么时候会用到它呢?哎,当你去调用这个方法的时候,你是不是要用到这个类了呀,那么就下载,而如果你没调用这个方法,它是不会这个,也就我们说的静态内部,静态内部不会自动随着啊这个外部类的加载和初始化,呃,初始化啊,初始化它是独立的一类,但虽然它是在那个里面啊,但它是要单独,它是要单独去加载和初始化的,所以呢,当你去用这个类的时候。
30:32
用这个方法的时候,那不用这个类了吗?用这个类的时候,他才会去交代个初始化,这样呢就可以延迟去创建。延迟去创建它的实力对象,而且又保证现场安全,因为我们是内在的器,那么因为啊是用这个是在内部内这个初始化石加载和初始化石,那么这个创建,那么因此是形成安全,现在安全是内加载器对吧,来完成工作。
31:03
那么它是限制安全,那么它比刚才的第五种,那么大家看一下可能会代码上面看起来呢,会更简洁一点啊,你这里就写了好几个if,还有同步宽容的,看起来可能没那么简洁,那么六就会简洁一点。那么这里呢,我们就给大家写了六种这样的这个单利模式,二函式和懒函式分别两大类,看你什么时候传染对象,那么可以总结一下对吧?如果二汉式,那么前没举形式是最简单的,如果是懒汉式,那么金态内容形式最简单,那么大家考量是吧?选择哪一种,那么你自己来看一下是吧。你需要恶汉式还男汗水,就取决于你是要什么时候创业,什么时机创业对象。那么现在安全问题,只要是二汉时都没有现安全问题懒汉时,那么有一种是现在安全问题的,其他的也都没有现在安全问题。好,那么今天呢,就讲到这里,谢谢大家。
我来说两句