00:01
好,前面呢,我们完成了商城的整个检索功能,那接下来我们再来开展其他业务之前,我们先来复一下我们接下来要用到的知识点,那么主要呢,来说一下我们E步和线程池的问题,那这一块呢,如果大家听过周阳老师讲的GC相关的课程,那么这一块呢就可以跳过了,那如果对这儿还不熟悉的,那我们就带着大家来快速的过一遍。首先我们来说,在我们这个JA里边,包括我们后来的业务开发们有非常多的异步场景,我们为了节约时间或者提高系统的吞吐量,我们要做一些异步任务,而这异步呢,在我们Java里边,我们都是使用thread,诶开启一个线程的方式,那这个开启线程的方式有几种呢?我们先来说一下我们第一个我们初始化线程的四种方式,那最基本的就是我们继承thread和实现runable接口,包括在我们JDK1.5级以后呢,添加了一个我们cornerable接口,这个corner包呢。
01:01
你这两个的优点就在于他们两个启动一个线程不会得到我们这个异步后台的这个返回值,而我们这个cornerb接口,它可以拿到我们这个异步执行的返回结果。当然呢,Corner要使用可以来结合我们的future task,这是我们说的这三种,还有最后一种,我们直接可以让线程池给线程池里边提交一个异步任务,让它初始化并来执行。好我们先来说一下我们这四种方式,我们先来回顾一下,那这四种方式我们把所有的测试代码好,我都来放在我们的这个包下,比如我们就叫thread,我们就叫thread test。而且呢,我们这个多线程测试的这一块,大家不能写测试类,测试类呢,测不出多线程效果好,我们就来用慢方法来测,首先呢,我们来测我们这四个我们创建。和初始化,我们做现成的几种方式,好,第一种我们继承thread,这个呢大家都来用过来,我就来简单的快速过一遍public static来写一个静态的内class,比如呢,我就叫THREAD01,我们来继承thread,所以我们想要实现一个多线程。第一种我们先来继承thread,它里边呢,我们要重写run方法,我们也想要异步运行的所有功能们都写在这儿,好,我们来先see out,比如我们这个当前线程,我们把这个当前线程打印一下,好,我们来拿一个thread.get current thread点有一个current thread点一个get ID,我们就拿一个线程号就行了。比如我们再来进行一项业务逻辑I等于我们这个十除二。
02:42
好,最终的结果呢,我们运行完了以后,我们可以来输出一个结果,好,我们这个运行结果,结果呢,我们把这个IE打印,这我们第一种方式,我们通过继承threat,那想要启动它也非常简单。由于呢,它直接是thread子类,所以我们呢就直接可以来拗一个thread,好,那么这个thread呢,拗出来,当然我们要拗的是这个THREAD01,把这个扭出来,那我们接口呢,变为这个THREAD01,我们想要启动它,那直接调用它呢,有一个start方法,这就是我们这个启动线程,那么这个线程呢,就会后台执行了,包括我们来看一下后台执行是什么效果,比如我们在慢方法一开始之前,那this out,来打印一下慢,诶我们的慢方法start慢方法开始了,包括我们后来呃,运行结束以后,我们就说慢方法结束了,End,我们来看一下效果,好,我先来运行。
03:42
好,我们看到呢,效果是这样,首先呢慢start,我们这个慢方法start先运行,然后呢立即会运行我们这个慢end,因为我们这个呢是后台才慢慢启动运行的,所以接下来呢,后台的东西才会在这打印,那没问题,我们这呢就启动了一个异步,那接下来我们继续我们还有第二种方式,我们来实现runable接口的方式,好我把这种呢放在这儿来,我们来看我们第二种实现runable接口,我们接下来呢,还可以这么来写public static来class,比如呢,我们就叫reable,我们就叫零一,我们呢可以通过实现reable接口的方式,好然后呢,Runable接口的话,那就需要有一个方法,就是我们来实现它的run方法,同样的业务逻辑,我把这个呢直接复制过来,我们想要启动它也一样,我们现在呢,想要启动线程总是要new一个thread,这是一个固定模式,我们只有new thread.start我们才能。
04:42
能启动线程,但是呢,我们接下来要启动它这个线程,那我们呢,就可以这样现来扭一个我们这个RUNABLE01,这是我们这个对象,它呢要以异步的方式来执行我们业务逻辑,那把它传到我们这个new thread start里边,那这样呢,我们这个异步任务又就开始了,好,我们把这个end呢还是拿过来,我们来看,这是我们runnerable的方式,我们再来启动来测试一下,这两个呢,我们以前都很常用,大家都见过。
05:14
好,那么效果呢也一样是这个,然后呢,我们再来看一下JDK1.5以后给我们添加的这个corniable,这个cornerniable呢跟roundable的区别是什么?我们还是来写一个类public,好,Static我们就叫static,我们的class class呢我们就来就叫column,我们就叫column的这个零一。然后呢,我们同样来实现我们有一个接口呢,就叫cornerable,这个接口呢,发现这一块呢,会有一个泛型,而这个泛型呢,其实就是我们方法的返回值accountable跟其他的这些runable thread都不同,它允许我们这个异步任务有返回值啊,比如呢,我们返回一个整数的计算结果,好来添加上它的这个实现,那Co呢,我们就得实现它接口提供的这个括方法,把我们业务逻辑拿过来,我们最终的返回结果能返回出去。
06:12
这是我们使用康宝,那康宝要怎么启动,那先来看一下,由于呢,它实现上的是康宝接口,但是这个接口呢,至此就结束了,我们说启动线程总是要new thread,那这个cornerable咋办呢?我们接下来就可以配合我们这个future task,我们可以用一个future task,那这个future task呢,来点进来给大家看,它呢可以允许传一个cornerable,所以呢,我们给task里边,那传一个我们的这个cornerable的对象,好我们来new一个,New一个我们的这个CORNERABLE0一来创建一个它传到我们这个future task里边,而这个future是什么?我们再点进来,诶我们发现呢,它实现了什么runnerable future再点进来,诶我们发现它其实就是runnerable,所以呢,相当于它也是可以来支持我们new thad运行的,也就说我们要启动cornerable,那么先把这个cornerable呢封装成我们的future task,然后呢,我们再可以使用new一个。
07:13
Thad点一个start,把我们这个future test,我们的异步任务给它放里边,点一个start,让它后台开启执行就行了,然后呢,最终我们返回,这就是N的结束,我们来测试一下我们的COB,结果好走好,这是我们cornerb也是一样的运行效果,但是呢,Cornerb最大的好处就是我们这个future task假设我们模拟它后台运行的很慢,但我最终还想要返回我们这个cornerb的计算结果,我们这个业务有一个计算结果,我们就可以拿到这个future test呢,它有一个方法叫点一个get,这个get呢,就可以等待我们这个异步任务执行完,最终返回结果,这就是我们的get get它的作用我们来说一下,这就是等待我们整个。
08:04
线程执行完成获取返回结果,所以呢,我们可以在这一块拿到我们最终的返回结果,我们来运行测试一下,我们现在来看一下效果好,效果呢,现在就变成这样了,我返回结果能拿到,而且大家看我们这个start跟end之间,我们发现呢,是我们这个线程的运行打印原因是啥?因为我们要等待这个返回结果,肯定就得等它执行完,那等他执行完我们这一行才会有结果,那下一行才能打印出结果,所以呢,我们这个是一个阻塞,等待阻塞们呢,一直要等待我们前边的所有方法执行完,我们的异步方法执行完了,我们才在这呢可以获取到结果,那这样呢,我们就可以在这儿通过get方法最终拿到我们的异步返回结果,我们来进行使用,就行了,这是我们说的cornerable,那这个cornerable的使用方法呢,就是唯一我们用future task可以来。
09:04
多加一个包装,包括这个future task,我们可以来看一下,它呢,不只能接受我们的cornerable,还可以来接受我们的runable,来往下边放,诶我们这个runable呢,也是可以的,包括呢。如果使用runable,我们还可以传一个对象,让这个对象来结束返回值。而想要让runable最终能有返回值也是可以的。使用第二个方式future task把我们renerable一传,只要renerable的方法里边,比如我们这个reable这个方法里边,最终的这个值我们不返回,而是复值给我们传过来的这个构造对象,那就没什么问题了。好,这是我们说的corner的方式,我把这个去掉,然后呢,那最后一种方式,我们想要整一个线程让它执行,那么直接可以使用线程池,给线程池里边来提交我们这个任务就行了,给线程池直接提交任务,它呢就会自己线程池里边就会开启一个线程,就像我们之前一样new一个thread,并把它start启动起来。但是呢,说到这我们就不得不说一件事,那我们为什么要用线程池?首先我们来考虑一件事情。
10:19
如果我们后来的业务逻辑里边,我们的异步非常的多,我们呢都是写我们原生代码,New一个thread,比如把我们runnerable的方法,我们来传进来。我们就一个hello,然后我们就给他start。我们每次要执行业务了,我们都直接开启一个线程start,开启一个线程start,这样有什么问题没有,我们说这个问题啊,其实是非常大的,这就类似于我们公司里边只要有一个新任务过来要干活,那我们就招了一个员工来,我们newsread就开一个县城,相当专门有人来干这个活,那么就招一个员工在干这个活。
11:01
只要有一个任务过来,我们就招一个员工,那最终肯定会将公司的资源耗尽,那我们这个业务也一样,因为我们最终给我们分分配的这个堆空间也好,占空间也好,我们内存是有限的,如果在我们高并发系统里边,只要请求一进来,我们比如这个业务非常大,要进行非常多的异步任务查询,我们现在有100万个请求进来,我们这个业务呢,假设一个就要开启十个异步任务,那么这100万个进来呢,直接一步任务,直接new start new start就开启了1000万个,那肯定会导致我们资源耗尽,而最终的系统崩溃。所以呢,我们以后在业务代码里边,我们先来说,我们以后在业务代码里边以上三种方式呢。启动线程的方式我们都不用以上三种,以上三种启动线程的方式都不用。
12:01
因为我们直接来new thread start,其实以上三种就是一种直接new thread start,可能会导致我们资源耗尽,最终呢,没有更多的资源来处理我们其他的更核心的功能,所以呢,我们依旧应该将所有的一异步任务来说一下,应该将将所有的多线程异步任务,异步任务都交给线程池执行,线程池执行这个就类似于什么,诶,我们公司员工现在就50个员工,有什么活,我们就分配给这50个人中的某一个,当然如果这个人呢,正在有活儿,那就等他处理完了再处理我们这个活,那这样做的好处就是我们达到了资源控制,我们总是只有这50个人,那消耗的资源也就是这50个人消耗的资源,所以呢,我们以后都应该遵循我们这个逻辑,特别是我们再来设计这些高并发系统的时候,我们一定要将所有的业务异步任。
13:02
都来交给线程池,线程池来进行可控的,那么就来看一下我们如何给线程池里边可以来直接提交一个任务运行,那这个线程池呢,我们先来说guc里边有一个最快的方式得到线程池,有个ex excuses,它里边呢可以创建出我们的线程池,比如我们先来用第一个new,一个fixed thread po。一个固定数量的线程池,这里边呢,传一个n stress相当于我写一个十,那就相当于我们这个线程池里边有十个线程,先空闲着就来等待接我们的异步任务,所以呢,我们就应该得到一个线程池,而且大家一定注意我们这个线程池呢。不应该是执行每一个异步任务上来创建一个池,而是应该我们整个系统一个线程池,大家都把自己的任务交给这个池里边执行,所以这个池啊,一定要保证我们系统里边最好只有一个,诶我们当前系统中池我们有一两个,哎,我们也不说一个,有些呢,可能我们整上两三个池。
14:12
有核心业务的,有非核心业务的,我们可能呢,池只能有一两个,然后呢,每一个异步任务,每个异步任务直接提交给线程池,线程池让它自己去执行就行,所以呢,我们来把这个线程池我来放到最前边,我们就来模拟我们这个系统呢,只有一个,那我们想要访问它就来加上public static,那至于这个线程池是什么线程池,我们一会儿还要精细的来讲,我们这个线程池好,我们把这个线程池拿过来,我们给它里边提交任务点一个,我们发现呢,这一块可以使用submit给线程池里边提交一个runable或者accountable的任务,或者呢使用execute让它执行,那submit跟execute的区别是什么?区别就在这,大家看,如果是submit给线程池里边提交一个任务,那么呢,我们是可以来获取到任务的返回值的,比如我们传的是一个corner。
15:12
哦,能获取到返回值,但是如果是execute,只是让线程持续来执行我们这个异步任务,我们呢就会是一个wild,那这两个呢,其实都是给它里边提交一个一步执行,好我就来写一个CU,里边呢传一个run runnerable,那这个runable呢,那可以像以前方式,我们直接用一个,我们写到这个RUNABLE01,我们传进去,它呢也会自动执行来看一下效果走,好我们看到呢,My start,包括我们这个呢,也执行了,当然我们这种呢,是使用线程池的方式,那们将任务呢提交给线程池,而不是我们自个儿在这儿new start,那通过这四种方式呢,我们都可以初始化并执行一个任务,使用我们多线程的方式,但是它们有什么区别呢?好,我们在最终的下边,我们来给一个区别,区别,那区别就是一二,我们来看一二这两种方式呢,它是不能得到返回值,不能得到返回值,而呢。
16:12
啊,我们第三种方式可以获取返回值,但是呢,一二三都不能达到我们这个资源控制的效果,都不能,咱们这个控制资源相当于我们只要new thread了,那就立即执行,只要new thread了就立即执行,每来一个活招一个员工,这样就会导致资源耗尽,那只有我们四的方式可以。控制我们这个资源,那我们现在呢,根据当前系统调配,诶我看当前系统呢,比如只有1G内存,顶多呢200多个线程来去执行,我测出了峰值等等,那么就直接给线程池里边控制好最大量资源,那这样呢计算即使就算有1000万个任务进来,那我们也只能两百两百两百的慢慢执行,而不是1000万的任务瞬间全部启动,我们就达到了资源控制的效果。
17:06
不会让我们瞬间资源耗尽,所以它给我们带来的好处就是我们整个系统的性能是稳定的,无论面对多高的并发,我们都不会导致资源耗尽的问题而让我们系统崩溃。所以呢,我们最终是稳定是第一,我们永远都是求稳定是第一。我们高并发写的再厉害,运行上十秒崩溃了,那也没啥用,所以呢,我们最终用的非常多的就是线程池,那我们后来业务里边我们要经常用它,那好下一节课呢,我们就来说一下详细的线程池里边的一些细节。
我来说两句