00:00
好,我们接着上节课讲啊。呃。讲完缓存对齐之后,我们讲CPU的这个。它的这个特点,这个特点叫什么呢?叫乱序执行,乱序执行是什么意思呢。很简单,看这个小例子,我们有两条指令,这是指令一,这是指令二。那么指令一呢,它是要去执行内存里头读数据。给内存发请求,然后等他返回,上节课我们讲过,它的速度和它的速度比100:1,所以你要等它返回,那你就老得等着了,浪费你自己的资源,浪费CPU的计算资源,所以这时候怎么办呢?他就会优先的。如果这两条指令。没有任何的依赖关系,就是说我指令二的执行过程,我不需要依赖指令一,好,在这种情况之下,这两条指令,指令二第二条指令可以先执行,那么站在程序的角度来看的话,你就会发现,啊,原来这条指令。
01:04
在这条指令之前就完成了,虽然你写的时候写的是指令一在前面,指令二在后面,但是呢,执行的时候指令二优先就执行完了啊,这个叫乱序执行。呃,当然我们不能称它为乱序,其实本质上呢,是在提高效率,多流水线式的执行。网上一个非常有名的图,就是那个啊,你洗水壶,烧开水洗茶杯,这样的话你才能泡茶喝茶。乱序执行的意思其实本质上是在同时执行,这边在烧开水的时候,我不用老等着,我直接把那个茶壶茶杯全洗了,最后体现出来的结果就是下面的这种执行方式,效率更低了,花的花的时间更少了,这叫做乱序。而这概念呢,非常简单。我们来证明他一下,乱序执行的证明,呃,我在这篇课里头呢,也给大家讲过这个小程序了,但是那次讲的我现在回想起来啊,其实不是很到位。
02:07
尤其是解释的时候,是从数学上来解释的,后来我想了一下,这东西没必要从数学来解释,所以我重新跟大家聊一下这个小程序。还从推理的推推了半天。后来我想,怎么那么别扭啊,没有找到比较好的。教大家的一个方式,好,大家看这里啊。Disorder小程序呢,也特别简单,呃,你学过这班课呢,这个小程序肯定读过了,我们是有四个值,S等于零,Y等零,A等零,B等零,XY和AB4个值,我们不断的尝试在两个线程里头分别干这么一件事,在第一个线程里。A等于一,X等于B,第二线程B等于一,Y等于A分析这个小程序的时候呢,就看。
03:01
这几条指令的执行,X等于一,A等一,X等BB等于一,Y等于A,那么你仔细分析一下A等一,Y等B这两条指令,当然从CPU的角度,这不是两条指令,这是有好多不同的不同条的指令,但是这两条语句它们之间是没有任何的。依赖关系的我Y等于B,我不需要依赖于上面这句话,执行完了之后我才能执行。而下面这个B等于一,Y等于A。我也不不,我我这个Y等A也不需要依赖于B等于先执行,所以从概率上来讲,如果CPU。能够说乱序执行的话,那么是有可能Y等A跑到B等于前面,或者Y等B。跑到A等于一前面。那么假如说啊,我们假如说它不能够乱序执行,那么你想一下啊,这两这两个线程的排列组合,你仔细分析一下。
04:02
不管你是A等于一,Y等于B,然后第二条线程才执行,那它的执行顺序变成B等于一,Y等于A,从时间顺序上来讲。啊,不管是这种组合,还是说我A等于一,然后马上这边B等于一,这边Y等于B,这边Y等于A这样来执行。也不管是A等于一什么。B这边是B等于一,然后Y等于A。然后Y等于B这种组合就是说这几条指令在时间上的排序的,不管各种各样的排序的组合,你永远都不可能得到一种结果,永远都不可能得出来X等零,Y等零的这种结果,你不信你自己去排就行了。所以一旦发生了这种情况,只能说明一个事情,就是。
05:00
要么X等于B排前面去了,要么Y等于A排前面去了。上节课讲的时候那个。没从排列组合的角度讲,从那个数学推理的角度讲啊,讲了半天,很多人呢,可能理解起来还费劲。其实排列组合你排列一下就是这四条指令啊,这四条语句你不论怎么排,在两个,在两个线程里头,不论你怎么排。如果他没有发生过这个乱序执行的话,如果没有发生过这个后面的指令排到前面去的话,是绝对不可能发生X等零,Y等零的。所以呢,我们做实验的时候,只要发生了X等于Y等于零的时候,那么说明一定一定。它发生了指令的重排序啊,这就能证明CPU里面呢,确实有指令重排序存在。当然我就不执行了,因为执行需要花好长时间,呃,我第一次做实验是272次的循环之后才得出了零零的结果,第二次是11万次,你们自己去执行就行了,好吧,呃,我的这个小程序呢,其实。
06:09
是来源于呃,国外一个哥们儿写的博客,这个链接呢,我我已经给到大家了,他是C写的啊。嗯。好笔记,给大家记一下。一个老外的博客,当然他是C写的,呃,看咱们自己程序的话呢,你就看那个。
07:00
GVM项目的GMM里面的那个disorder那个小程序啊。Is order。从排列组合角度一看,你就看明白了,好吧。还没见过其他的,为什么要见其他的一定要见啊,嗯。OK。证明它存在就可以了。当然你证明它存在之后呢,就会产生一系列的问题,就是说呃,它存在就存在吗?假如它存在没有任何问题的话,那就让他存在着呗。不过如果它存在在多线程的情况下,会可能会有一定的问题。我这道题我讲过N多次了,你听过老师以前课呢,也一定是听过了,但是为了课程完整,完整性起见,我再简单过一遍,这也是美团的一个问题,他叫DC单位,为什么要加volatile?
08:09
为什么要加vola啊?因为创建一个对象的时候,对象的创建过程是有中间的,一一个一个111个状态,就是当我们利用一个对象出来的时候,首先你它的成员变量会是什么样的,成员变量会是中间有一个中间态class t m等于八,当我们扭出这个T来的时候。在汇编码上。Java的汇编码上。New这个对象首先是六了一块内存,但是这块内存里面M的这个值,它是等于它的默认值,这默认值是几十零。基本上所有基础变量的值是零。如果你是一个引用类型的变量,那么它的默认值就是空值now。然后才执行它的构造方法,执行完构造方法之后呢,才执行。
09:05
两个之间建立关联。a store。呃,讲公开课很多人没有学过汇编码,咱们是学过一定的汇编码的,所以现在呢,我给大家解释的时候就可以稍微解释的详细一些。New new一个出来的时候,只是在内存申请一块空间,只有执行完构造方法,Special构造执行,执行完构造方法之后,这个M才会变成八。那么duplication Du这条指令叫duplication啊,Duplicate sorry,叫duplicate duplicate啊,叫这这句话什么意思呢?他念完之后呢。在你的自己的战里面,就是这个分法的战争里面,它实际上呢,会存一个引用,这个引用指向这个对象。注意啊,它并不是成员这个本地变量表,不是这个T。
10:04
然后dup是什么?Dup的意思就是说在他在上面的再复制一下这个站里面的内容,再复制一份。Duate再复制一份,复制一份之后in special这条指令它会消耗一个值,它会把这个值弹出来,弹一个出来。把它指向的这个对象,调用它的构造方法。注意,这时候你的占里面只剩一个了,A store one的意思是把这个东西弹出来。赋值给我们的局部变量表的排在第一个的位置,我们第零个位置,大家还记得是什么位置吗?来,哪位同学告诉我一下,就变量表的第零个位置a store,零是什么?对,是this,没错。这M项目要找老师拉进去吗?不需要,不知道在哪找,就找到咱们的班主任老师,让他告诉你。呃,如果你还有印象的话,这几条指令应该你全认识啊,新报名的同学,如果你没学过,这边翻过头去看。
11:09
这里面主要有一条指令叫Du Du Du duplicate,那么这条指令呢?呃,有的同学不熟悉啊,其实这个意思就是special,它需要消耗一个引用,所以呢。在。他在上面复制了一下,不然的话,你把你把那个最底上那个弄没了之后,你没法向这个T来复制了,所以a do结束之后,其实这个T呢,才会指向我们的M等于八。这个意思。由于创建过程有一个中间态,所以你加单叫double check lock的时候单我讲过了,你在这不重复了。因此呢,第一个建成。要运行的时候,它会检查说,呃,你是不是为空,当然你不不为空啊,Sorry,你还是空值,因为敌滴线程还没有对T进行初始化。
12:01
它仍然是空值,所以我拗拗出来的时候,注意正好拗到一半的时候,他有可能会发生指令的重排,那有的同学就这时候就一定会问我说,老师,这个指令重拍的规则有没有什么样的指令可以重拍,什么样的指令不能重拍?你们,你们应该想问吧。对不对。嗯,对,没错,一定有这个问题,好,我一会儿讲给你听,在Java里面规定了一些不能重排序的情况,其他的情况下都可以重排序,我可以负责任的跟你讲,这种情况是可以重排序的。如果发生指令重排序,那就相当于你这个T提前的指向了半初始化状态对象,正好在这个时候另外一个线程来了,它检查T不为空,所以就直接使用了半初始化状态对象。因此,这也是为什么在dcr里面你必须要加volatile的原因,因为vola有一个作用叫做禁止指令重排序。
13:03
好,这相当于给大家复习了一下以前讲过的内容啊,我就在这不重复了,呃,我们可以。继续往前了吧,没问题吧。嗯。好。这段这段听着你如果稍微有问题的啊,翻以前的咱们的课程的内容去好吧。倾听语落,我就是遇到了听不懂的面试官,然后觉得我是在乱说what。嗯。好看这里既然发生了指令重排,那么而且我也向你证明了,如果指令重排,你要是不好好让他给这个这个这个不好好控制它的话,它一定会产生问题,所以呃,这个时候,那么他到底是怎么样才能够禁止指令重排呢?就是有一些指令,我不想让他进行重排序的时候,我禁止指令重拍,我该怎么去实现它,我们说CPU层面的实现。
14:12
CPU层面的实现是通过内存屏障来的。内存屏障的意思也是特别简单,你看下面这个图就理解了,中间这道线就是一个内存屏障指令。我们有两条指令,指令一,第二条指令,指令二。我如果不想让指令二线执行,我怎么办呀?我在中间加一道屏障,屏障的意思是上面这条指令和下面的指令不可以互换位置。你到这儿的时候,他就执行到一个屏障了,就得给我等着。什么时候上面指令全执行完了,下面指令才能继续执行,这叫做屏障。内存屏障指令一二不可以进行互换。当然,它的底层是通过什么来实现,我告诉你,作为英特尔CPU来说,它提供一些原语。也就是汇编指令。
15:00
Elephantence menence senence,呃,Elephant fence l叫load读读屏障,S fence s叫save写屏障。有你你现在有有同学不了解这个读篇写篇章什么意思的。M fence叫读写凭证,这个M是什么缩写的不是很清楚,叫应该叫,叫就是memory fence啊。Memory内存凭证就是所有读写凭证啊,Mixed啊,有可能是mixed啊,对应该是mixed啊。咱们有美国的小伙伴在,还是起来比较省事。缩起他。Mixed,对,应该是mixed,你们说的啊,不,不是不是memory sorry。所以它有三个原语来实现。呃,当然也可以使用总线索来实现总线索,上节我说过这这是。它可以用来实现一切的这种关于原子性的一些东西,还有印象吧,嗯,所以呢,当然可以用总线,总线锁来实现,注意这是CPU层面来实现的。
16:10
如果你要写汇编指令,你要自己写汇编程序,两条指令之间不想让它互换怎么办?你就用原语来实现。我们的volatile的实现,原来我也讲过,它并不是什么来实现的呢,它其实并不是用这个原语来实现,它用的是lock指令,就是锁锁锁屏障啊呃,所以在CPU这个层面上的有序性的一个保障就是用sence elephantence menence。来,记点笔记。顺序执行。
17:03
嗯。禁止乱序。那么作为禁止乱序来说,CPU层面。Inner inner怎么实现的呢?它是使用原语就汇编三条。Fans as fans as fans,呃,当然这是硬件实现,或者什么,或者锁总线,当然这是硬件实现。呃,需要注意的是呢。所谓的GVM的实现和这个实现是不是一个概念,因为原来咱们就一直讲过GVM是个什么东西,JVM呢?它是一个规范,它只是要求啊,你必须得有些东西不许给我排序,但是你具体自己怎么去实现的,那不好意思,你就看你的GVM的house虚拟机到底是怎么写的这意思。
18:18
嗯。我刚才说了三条指令可以实现,或者是使用什么实现呢?使用lock指令,Lock指令是老大。Law x86上有一条lock指令,它是一个副berry。就是它会禁止上下所要的不管是读指令还是斜指令都不能进行重排序,执行的时候会锁住内存子系统,也就是锁内存那条总线来保证确确实保证整个执行顺序跨越多多个CPU,所以这个呢是一个硬件上的实现,而软件的这种所loft software locks通常使用内存凭证或者原子指令。来实现变量可见性和保持程序程序顺序。呃,原来我们讲volatile的底层实现的时候就说过volatile底层实现大家还有印象吗?它是拿拿什么来实现的?
19:08
如果有同学有印象的话,它是这条指令lock。L。No nono nononono,不对,不是lock exm,你,你说的应该是那个lock compare and exchange。Compare and exchange这条指令,这是synchronize的底层synchize,由于它是一个入口,所以你必须是自旋式的完成。拿到某一个值之后,拿到这把锁之后才能继续往下执行,所以它叫lock compared exchange,而volatile是什么?Volaile是一个呃,只是一个屏障,它没有必要说呃,用一个自旋去改一个值,所以它执行了一条空指令。这个二代L是干了一件什么事呢?在某一个寄存器上加了一个零。我忘了那个寄存器名字了,你们自己去查某个寄存器的值上加一个零,加零什么意思?是不是相当于什么都没加吗?其实就是一条空指令,所以它本质上就执行这条指令。
20:10
而lock。有同学说你lock指令,为什么lock那个LP啊,那个指令啊,那个指令本身就是什么都不做不执行,可是这条指令不能跟lock混在一起用,所以他用了这么一条指令,叫lock at l。加了一个零。啊,风雨冷人已经写出来了,对。Al叫00ESP,在就是往EP这个寄存器上给它加了个零。你好,想一下这个加零加零这件事儿是什么意思。对这个寄存器没有任何操作。相当于。但是他主要想执行这条lock指令。好,Log指令一执行,整个所有关于内存前面的东西都得给我执行完了,刷到内存里面去,后面的指令才能执行,所以这是一个大的凭证,特别大个。
21:13
OK。我再说一遍啊,这是硬件上的两种保证顺序的执行方式,从效率来讲,你绝对使用言语要比这条lock指令效率要高。在硬件层面,但是很不幸的是呢,像这种源于英特尔CPU有其他CPU不一定有,但是像这条lock指令很多CPU都有。所以呃,GM在实现就house在实现的时候。好,他就偷懒啊,他并没有针根对根据不同的这种呃CPU呢,去设计那个不同的CPU的言语这样来执行,而是说就用了一条lock指令来执行。OK,不知道我说清楚没有,能get到的同学给老师扣,一有问题你直接提。
22:05
啊,冯玉老师说对啊,周志明老师那本书有说对没错。嗯。好,看这里,下面我们说GVM层级的执行。书还没到啊,老师的书我都看完了,你你的书还没到。什么情况?好,看这里啊。嗯。这是JVM要求的一个实现。注意这个东西呢,是一个。虚拟机规范级别的,它跟硬件没有半毛钱关系,呃,网上很多文章呢,会把这东西和硬件的那个内存名称混在一起讲,所以这时候你读起来会云山雾罩的,我再说一遍,它是不同层面的。我们说你写一个程序啊,你写一个Java程序,Java程序呢,要求你某一些指令不可以进行重排序,那也就要求你在JVM层级,你需要有一些凭证,所以这个是GVM的一个规范要求。
23:11
然后呢,你得写那个JVM的实现的hotport吗?Houseport实现,你到底拿什么去实现,你用LFM原以实现,或者是用lock指令来实现。这是我们刚刚讲的是CPU的底层实现,好,这里我们来聊JVM它的一个要求。GVM的规范里头的要求的内存凭证,它有四种,Load load store store load store store load其实特别简单。Load load的意思就是说我加一个屏障,上面有一条load指令,下面有一条load指令,这两条load指令不可互换。剩下的我觉得我不用讲了啊。Store store就是上面一条store指令,下面一条store指令不可互换。OK。
24:00
看这里。所以GVM层级对于volatile的一个要求,Volatile的一个规范的要求,它特别的保守,他。他是这么干的。仔细读一下,看能不能读懂。如果你对某一块内存。进行了一个修饰的话,一个修饰这块内存。好。它前面有一个屏障叫store store后面有个屏障叫store load,你仔细分析一下。前面有一个屏障叫store store后面有一个屏障叫store load,其实你中间那条指令写写操作不就是store吗?写嘛,对不对。也就是上面如果有store,你和下面这个store绝对不可能互换,这两个互换是不可能的。写操作,下面这个store load,你现在不是读取吗?得必须得等我写完了,你下面才能读。
25:01
好,这就保证了。可见性,而且还不还还还不会有重排序。而独操作呢?这里就是个漏操作嘛,它上面这个漏操作不可以互换。下面这个写操作不可以互换,得等我读完了你才能写,可见性不可重排序就全保障了,所以GVM层级对于的底层实现就是这么这么来实现的。我希望你过。SX去。阿里面试的时候。呃,SX是去阿里面试的时候,还是去哪儿忘了啊,你你们你们可以骚扰骚扰他问问。是被问到过volatile的在GM层级实现到底是怎么实现的?当然。其实呢,你想不起来,你至少应该能想出一个什么东西来呢,就是使用内存屏障来实现呢。
26:00
呃,然后呢,在写操作和读操作前后都加了屏障,所以它实现上非常的保守,我再说一遍,这是JVM层级,就是说JVM要求要这么实现,具体你是拿什么实现这个屏障的。还是那条lock指令。其实。好,看看这里,大家伙有什么有疑问的地方没有,因为这个呢,牵扯到以前讲过的一些东西,我讲的比较快,怕有些同学有疑问。后续所有的load吗?还是就后面一条load,后续所有load呀,后续所有load。屏障,屏障就是你前面都得执行完了,执行完我这个写操作之后,你后面才能执行。Well,如果修饰对象成员变量进行写的话,也会实现屏障吗?哎,Green,你问到了一个初级核心的问题。Gray问的好啊。呃,我们一般做实验的时候,只是做这种实验叫volatile。
27:04
Int类型是吧,What,一个一个值I好,那么内存里头呢,它就是一块值,对它写,对它读,然后前后加篇章。你要是有一个对象呢?小T,它指向了一个拗出来的T对象,好,我个人的实验这边这个这个这个资料我一直没有查到,没有一个很官方的一个说法,我个人的实验是。你们自己去做实验好吗?做出来有不同的后果的。不同的效果的,你跟老师说一声啊,感谢我个人的实验是这个volatile小T。一般我们正常的理解,它会加在我们这个小T这块内存上一个引用,对这个引用进行读写的时候啊,前后加屏障,可是我个人实验室实际上是扭出来的这个对象上,整个这块内存空间,只要他有任何的改动,前后都加屏障。
28:02
好,刚才这一小段能get到的同学给老师扣一查了好多书都没有查到啊。所以你没有查到的,跟老师说一声啊。有时候我实在查不到我这个就不跟他较较劲了。其实再说一遍这个东西啊。嗯,你说你开发这种肯定是1万%都用不上的,直接加拉就行了,你要知道的话,而且实在不行你就用synchronized就完了,就是面试的时候,刚才小伙伴谁谁说的,面试的时候面试官听不懂怎么办,这面试官听不懂就真麻烦了。面试官听不懂,他会认为你说的不对。这个没招啊。你相当于帮了他了啊,就教了他点东西好,我们说before的原则,刚才我说过了,我说。呃,哪些东西可以进行重复排序,哪些东西不可以进行呀?其实在CPU这个级别,CPU的规则和你的JVM规则是不一样的,CPU规则在CPU看来,我管你哪条指令的,只要是前后不依赖,没有依赖关系,我就可以乱续执行,这是CPU的规则。
29:14
GM这个规则是这样的,它定立了八条happens before原则,就是在某些情况下不可以重排序,有些必须先发生,叫happens before原则。这就是网上有很多很多文章写的这个东西happens before,但是很多文章都写没有写明白,说这个happens before到底干什么用的,其实就是规定在某些特殊情况下,你不可以进行重排序的这个东西要不要背,不要背,你千万不要去背这玩意儿。你要背这个,我跟你说这就废,这这这这就太无聊了,也没有人给你考这个,有人要问到你说啊,是有八条这么一个规则,但是我肯定是背不过他。Volatile。还有呃,线程启动,线程终止,你要仔细,愿意去读一读的话,你会发现这些都是非常自然的要求。
30:05
一个unlock操作必须先对于同一所的lock操作,后面执行。这不是废话吗?这肯定的啊,好,看这里。呃,你如果想读这八条指令,在哪里去读GSGS叫做Java language specification Java语言规范两本书,我记得我跟大家说过啊。嗯,给它找出来work。Books。嗯。这两本书一个叫JLS,一个叫GVMSJVM就Java virtual machine specific GLS呢叫做Java language Java language specific。呃,Java语言的规范。在这个语言规范里头,他要求了Java语言。OK,前后的一个需要执遵守的这么一个八条happen before原则在哪里呢?在第17章第,呃,第第第4.5节啊,第17章第4.5节你们自己去看就行了,好吧。
31:13
好,我们读网上文章的时候,还有一个词叫做as if cereal,很多人理不理解这个词到底什么意思,As if cereal as if像是一个像是顺序执行的。什么意思?其实很简单,就是你的指令不是在CPU执行,内部执行的时候,它有可能是乱的吗?并不是按照一定的顺序来执行的。但是在单线程执行结果不变,就是说你在一个单线程里头,你这个不管你CPU的内部怎么换来换去,最后执行的结果,最终的一致性总是呢能够达到我们想要这个结果,X等一,Y等B这两条在内部执行的时候换一下没有关系的,在一个线程里头换一下没有任何关系。他先赋值,他先赋值有什么关系吗?没有关系。
32:01
所以在这种情况下。单线程执行结果不一样,看上去就像是顺序执行一样,所以它叫做as if sre。OK,不知道说清楚没有?该到的同学老师扣一。这几个名词啊,这三层级四八个happens before原则。四个内存屏障。Load load load store store load store store好。
33:05
嗯。所谓的as if cereal。不管硬件什么顺序,单线程执行的结果不变。看上去像是。Cereal的,OK,这个是as if cere的含义,有的同学老问,因为他那个那个看网上文章的时候是as if CE到底是个什么东东,理解不了,其实非常简单啊,非常非常简单,那在这我想拓展一个小问题,呃,同学们,那个假如说我们有这么一个系统。
34:04
然后系统里面有好多好多。请求过来,好多好多请求过来,我想把这些请求给放到。按照按照按照请求来的时候,我想给他,呃,原来呢,我们是可以使用什么线程池啊,然后把这些请求呢,分别进行处理,假如我想把这些请求放到。前后的挨着排的顺序执行,要求顺序执行。谁先到的,谁先执行,要求顺序执行啊,这样的一个执行方式,我我我应该用什么来做。有没有同学?了解的。这是一个轻微的面试题,最近刚刚遇到的啊,队列啊,对队列,你你只是扔到队列里,怎么执行啊?堆,你扔到堆里的话,你是打算让他有优先级吗?哎,旧城少年说的很对啊,大家记住这个答案,旧城少年说的非常对。
35:08
直接用什么呢?Thread pool里面有一个叫single。Thread pool还有印象吗?叫single thread pool,单线程的。那里面是个队列,他自己维护那个队列,你往里扔就可以了。哎,用那个啊。突然想起看到这个as if cere,呃,突然想起了一件事来了,这样也行。Join的话就有一定的顺序了。而且转一般是你得。一个一个一个一个111个任务,一个任务就是可以分成好多好多子任务的时候,这个一般时候用join其他的嗯,用的不太多啊,也有不多,一直不会造成内存溢出吗。
36:06
呃,Utopia有utopia去翻多线程高并发的课程去造,不造成内存一出就看你的队列,你队列可以做有界队列呀,你的队列做成有界队列肯定不会内存溢出呀。游击队列扔不进去之后怎么办?拒绝策略吗?就特别这个是面试经常会问到的啊。就是一个队列。你给它长度有限制,你往里扔,你就不会产生内存溢出呀,对吧,那扔满了之后怎么办?拒绝策略嘛,面试经常问。就是说满了怎么办,新来的怎么处理,这就是你自己的拒绝策略啊。无界队列肯定会有可能会产生溢处的啊这个呃,问出这个同学问出这个问题,同学应该是没有听那个GM,呃,Sorry,没有听多线程的课,你翻完身去听一听,呃,尤其是有一些。
37:12
什么的,像阿里之类的,他们的写写写写代码的规范,其中有一条就是你不能够用那个catch threat tool,我不知道大家还有没有印象,因为正是因为里面的它这个队列呢,是接近于无限的。的最大值吗?很容易产生内存溢出,所以呢,不允许用那个线程池啊。对。不是65535啊。好了,不多说了啊,这是禁止乱序。
我来说两句