沧州三亚菏泽经济预测自然
投稿投诉
自然科学
知识物理
化学生物
地理解释
预测理解
本质社会
人类现象
行为研究
经济政治
心理结构
关系指导
人文遗产
菏泽德阳
山西湖州
宝鸡上海
茂名内江
三亚信阳
长春北海
西安安徽
黄石烟台
沧州湛江
肇庆鹤壁
六安韶关
成都钦州

高并发编程之Netty高性能调优工具类解析

8月20日 游鱼坊投稿
  1。1多线程共享FastThreadLocal
  我们在剖析堆外内存分配的时候简单介绍过FastThreadLocal,它类似于JDK的ThreadLocal,也是用于多线程条件下,保证统一线程的对象共享,只是Netty中定义的FastThreadLocal性能要高于JDK的ThreadLocal,本章开始我们来分析其具体原因。1。1。1FastThreadLocal的使用和创建
  首先看一个最简单的Demo。
  从上面示例中看出,首先声明一个内部类FastThreadLocalTest继承FastThreadLocal,并重写initialValue()方法。initialValue()方法就是用来初始化线程共享对象的。然后声明一个成员变量fastThreadLocalTest,类型是内部类FastThreadLocalTest,在构造方法中初始化fastThreadLocalTest。main()方法中创建当前类FastThreadLocalDemo的对象fastThreadLocalDemo,然后启动两个线程,每个线程通过fastThreadLocalDemo。fastThreadLocalTest。get()方式获取线程共享对象,因为fastThreadLocalDemo是相同的,所以fastThreadLocalTest对象也是同一个,同一个对象在不同线程中进行get()。第一个线程循环通过set()方法修改共享对象的值,第二个线程则循环判断fastThreadLocalTest。get()获得的对象和第一次get()获得的对象是否相等。这里输出结果都是true,说明其他线程虽然不断修改共享对象的值,但都不影响当前线程共享对象的值,这样就实现了线程共享对象的功能。
  根据上述示例,我们剖析FastThreadLocal的创建,FastThreadLocal的构造方法的代码如下。
  这里的index代表FastThreadLocal对象的一个下标,每创建一个FastThreadLocal都会有一个唯一的自增的下标,跟进nextVariableIndex()方法。
  上述代码中,获取nextIndex通过getAndIncrement()进行原子自增,创建第一个FastThreadLocal对象时,nextIndex为0;创建第二个FastThreadLocal对象时,nextIndex为1;依此类推,第n个FastThreadLocal对象时的nextIndex为n1,如下图所示。
  回到Demo中,看线程中的这一句。
  这是调用了FastThreadLocal对象的get()方法,作用是创建一个线程共享对象。get()方法的代码如下。
  这里调用了一个重载的get()方法,参数中通过InternalThreadLocalMap的get()方法获取了一个InternalThreadLocalMap对象。我们跟到InternalThreadLocalMap的get()方法中,分析其是如何获取InternalThreadLocalMap对象的。
  这里首先获取当前线程,然后判断当前线程是否为FastThreadLocalThread线程,通常NioEventLoop线程都是FastThreadLocalThread,用于线程则不是FastThreadLocalThread。
  如果是FastThreadLocalThread线程,则调用fastGet()方法获取InternalThreadLocalMap,从名字上我们能知道,这是一种效率极高的获取方式。
  如果不是FastThreadLocalThread线程,则调用slowGet()方法获取InternalThreadLocalMap,同样根据名字,我们知道这是一种效率不太高的获取方式。
  因为我们的Demo并不是EventLoop线程,所以调用slowGet()方法。
  首先剖析slowGet()方法。
  通过UnpaddedInternalThreadLocalMap。slowThreadLocalMap获取一个ThreadLocal对象slowThreadLocalMap,slowThreadLocalMap是UnpaddedInternalThreadLocalMap类的一个静态属性,类型是ThreadLocal类型。这里的ThreadLocal是JDK的ThreadLocal。
  然后通过slowThreadLocalMap对象的get()方法,获取一个InternalThreadLocalMap。如果是第一次获取,InternalThreadLocalMap有可能是null,则在if块中新建一个InternalThreadLocalMap对象,并设置在ThreadLocal对象中。
  因为Netty实现的FastThreadLocal要比JDK的ThreadLocal快,所以这里的方法叫作slowGet()方法。
  回到InternalThreadLocalMap的get()方法。
  继续剖析fastGet()方法,通常EventLoop线程创建FastThreadLocalThread线程,所以EventLoop线程执行到这一步的时候会调用fastGet()方法。
  首先FastThreadLocalThread对象直接通过ThreadLocalMap获取ThreadLocalMap对象。如果ThreadLocalMap为null,则创建一个InternalThreadLocalMap对象设置到FastThreadLocalThread的成员变量中。
  我们知道,FastThreadLocalThread对象中维护了一个InternalThreadLocalMap类型的成员变量,可以直接通过threadLocalMap()方法获取该变量的值,也就是InternalThreadLocalMap。
  跟进InternalThreadLocalMap的构造方法。
  这里调用了父类的构造方法,传入一个newIndexedVariableTable(),代码如下。
  这里创建了一个长度为32的数组,并将数组中的每一个对象都设置为UNSET,UNSET是一个Object的对象,表示该下标的值没有被设置。
  回到InternalThreadLocalMap的构造方法,看其父类的构造方法。
  这里初始化了一个数组类型的成员变量IndexedVariables,就是newIndexedVariableTable返回Object的数组。可以知道,每个InternalThreadLocalMap对象中都维护了一个Object类型的数组,那么这个数组有什么作用呢?继续往下剖析。
  回到FastThreadLocal的get()方法。
  剖析完InternalThreadLocalMap。get()的相关逻辑,继续看重载的get()方法。
  首先看这一步。
  这一步是获取当前Index下标的Object,其实就是获取每个FastThreadLocal对象绑定的线程共享对象。Index已经分析过,是每一个FastThreadLocal的唯一下标。
  跟进indexedVariable()方法。
  首先获取indexedVariables。indexedVariables是InternalThreadLocalMap对象中维护的数组,初始大小是32。然后在return中判断当前Index是不是小于当前数组的长度,如果小于则获取当前下标Index的数组元素,否则返回UNSET,代表没有设置的对象。
  其实每一个FastThreadLocal对象中所绑定的线程共享对象,都存放在ThreadLocalMap对象中的一个对象数组中,数组中元素的下标对应着FastThreadLocal中的Index属性,对应关系如下图所示。
  回到FastThreadLocal重载的get()方法。
  根据以上逻辑,第一次获取对象v时只能获取到UNSET对象,因为该对象并没有保存在ThreadLocalMap中的数组IndexedVariables中,所以第一次获取在if判断中为false时,会执行到initialize()方法中。跟到initialize()方法中。
  首先调用initialValue()方法,这里的initialValue实际上调用的是FastThreadLocal子类的重写initialValue()方法。在Demo中对应这个方法的代码如下。
  通过这个方法会创建一个线程共享对象。通过ThreadLocalMap对象的setIndexedVariable()方法将创建的线程共享对象设置到ThreadLocalMap维护的数组中,参数为FastThreadLocal和创建的对象本身。
  setIndexedVariable()方法的代码如下。
  先判断FastThreadLocal对象的Index是否超过数组IndexedVariables的长度,如果没有超过,则直接通过下标设置新创建的线程共享对象。通过这个操作,下次获取该对象的时候就可以直接通过数组下标进行取出。
  如果Index超过了数组IndexedVariables的长度,则通过expandIndexedVariableTableAndSet()方法将数组扩容,并且根据Index通过数组下标的方式将线程共享对象设置到数组IndexedVariables中。
  以上就是线程共享对象的创建和获取的过程。1。1。2FastThreadLocal的设值
  FastThreadLocal的设值是由set()方法完成的,其实就是通过调用set()方法修改线程共享对象,作用域是当前线程,我们回顾上一节Demo中的一个线程set对象的过程。
  set()方法的代码如下。
  首先判断当前设置的对象是不是UNSET。因为不是UNSET,所以进到if块中。
  if块调用了重载的set()方法,参数仍然为InternalThreadLocalMap,同时,参数也传入了set的Value值。
  跟进重载的set()方法。
  这里重点关注if(threadLocalMap。setIndexedVariable(index,value))这部分。通过ThreadLocalMap调用setIndexedVariable()方法进行对象的设置,传入了当前FastThreadLocal的下标和Value。setIndexedVariable()方法的代码如下。
  这里的逻辑其实和get非常类似,都是直接通过索引操作的,根据索引值,直接通过数组下标的方式对元素进行设置。
  回到FastThreadLocal的set()方法。
  如果修改的对象是UNSET对象,则会调用remove()方法,代码如下。
  ObjectvthreadLocalMap。removeIndexedVariable(index)这一步是根据索引Index将值设置成UNSET的。
  跟进removeIndexedVariable()方法。
  这里的逻辑也比较简单,根据Index通过数组下标的方式将元素设置成UNSET对象。
  回到remove()方法中,if(v!InternalThreadLocalMap。UNSET)判断如果设置的值不是UNSET对象,则调用onRemoval()方法。
  跟进onRemoval()方法。
  它是个空实现,用于交给子类去完成。1。2Recycler对象回收站
  Recycler我们应该不陌生,因为在前面章节中,有很多地方使用了Recycler。Recycler是Netty实现的一个轻量级对象回收站,很多对象在使用完毕之后,并没有直接交给GC去处理,而是通过对象回收站将对象回收,目的是为了对象重用和减少GC压力。比如ByteBuf对象的回收,因为ByteBuf对象在Netty中会被频繁创建,并且占用比较大的内存空间,所以使用完毕后会通过对象回收站的方式进行回收,以达到资源重用的目的。1。2。1Recycler的使用和创建
  在Netty中,Recycler的使用是相当频繁的。Recycler的作用是保证对象的循环利用,对象使用完可以通过Recycler回收,需要再次使用则从对象池中取出,不用每次都创建新对象,从而减少对系统资源的占用,同时也减轻了GC的压力。先看一个示例。
  首先定义了一个Recycler的成员变量RECYCLER,在匿名内部类中重写了newObject()方法,也就是创建对象的方法,该方法是用户自定义的。newObject()方法返回的newUser(handle)代表当回收站没有此类对象的时候,可以通过这种方式创建对象。成员变量RECYCLER可以用来对此类对象回收和再利用。然后定义了一个静态内部类User,User中有个成员变量Handle,在构造方法中为其赋值,Handle的作用是用于对象回收。并且定义了一个方法recycle(),方法中通过handle。recycle(this)这种方式将自身对象进行回收,通过这步操作,可以将对象回收到Recycler中。以上逻辑先做了解,之后会进行详细分析。
  在main()方法中,通过RECYCLER的get()方法获取一个User,然后进行回收,再通过get()方法将回收站的对象取出,再次进行回收,最后判断两次取出的对象是否为同一个对象,最后结果输出为true。以上Demo就可以说明Recycler的回收再利用的功能。
  简单介绍完Demo,我们就来详细地分析Recycler的机制。在Recycler的类的源码中,我们会看到这样一段逻辑。
  这一段逻辑用于保存线程共享对象,而这里的共享对象,就是一个Stack类型的对象。每个Stack中都维护着一个DefaultHandle类型的数组,用于盛放回收的对象,有关Stack和线程的关系如下图所示。
  也就是说,在每个Recycler中,都维护着一个线程共享的栈,用于对一类对象的回收。Stack的构造方法的代码如下。
  首先介绍几个构造方法中初始化的关键属性。
  Parent:表示Recycler对象自身。
  Thread:表示当前Stack绑定的哪个线程。
  maxCapacity:表示当前Stack的最大容量,表示Stack最多能盛放多少个元素。
  elements:表示Stack中存储的对象,类型为DefaultHandle,可以被外部对象引用,从而实现回收。
  ratioMask:用来控制对象回收的频率,也就是说每次通过Recycler回收对象的时候,不是每次都会进行回收,而是通过该参数控制回收频率。
  maxDelayedQueues:稍微有些复杂,很多时候,一个线程创建的对象,有可能会被另一个线程所释放,而另一个线程释放的对象不会放在当前线程的Stack中,而是存放在一个叫作WeakOrderQueue的数据结构中,里面也存放着一个个DefaultHandle,WeakOrderQueue会存放线程1创建且在线程2进行释放的对象。
  现在我们已经知道,maxDelayedQueues属性的意思就是设置该线程能回收的线程对象的最大值。假设当前线程是线程A,maxDelayedQueues值设置为2,那么线程A回收了线程B创建的对象,又回收了线程C创建的对象,就不能再回收线程D创建的对象,最多只能回收2个线程创建的对象。
  属性availableSharedCapacity表示在线程A中创建的对象,在其他线程中缓存的最大个数,同样,相关逻辑会在之后的内容进行剖析,另外介绍两个没有在构造方法中初始化的属性。
  这里cursor、prev和head是存放了其他线程的链表的指针,用于指向WeakOrderQueue,也是稍作了解,之后会进行详细剖析。有关Stack异线程之间对象的关系如下图所示。
  继续介绍Recycler的构造方法,同时熟悉有关Stack各个参数的默认值。
  这里调用了重载的构造方法,并传入了参数DEFAULTMAXCAPACITYPERTHREAD。DEFAULTMAXCAPACITYPERTHREAD的默认值是32768,是在static块中被初始化的,可以跟进去自行分析。这个值就代表每个线程中Stack中最多回收的元素的个数。继续跟进重载的构造方法。
  这里又调用了重载的构造方法,并且传入32768和MAXSHAREDCAPACITYFACTOR。
  MAXSHAREDCAPACITYFACTOR的默认值是2,同样在static块中进行了初始化,有关该属性的用处稍后讲解。继续跟进构造方法。
  这里同样调用了重载的构造方法,传入了32768和2,还有两个属性RATIO和MAXDELAYEDQUEUESPERTHREAD。RATIO也在static中被初始化,默认值是8。同上,MAXDELAYEDQUEUESPERTHREAD的默认值是2倍CPU核数。继续跟进构造方法。
  分析Recycler构造方法,主要就是将几个属性进行了初始化。
  ratioMask:它是获取safeFindNextPositivePowerOfTwo()方法的返回值。在此处safeFindNextPositivePowerOfTwo()方法的返回值是8,因此ratioMask的最终赋值是7。
  maxCapacityPerThread:它是一个大于0的数,如果不满足判断条件就进入else代码块,最终会被赋值为32768。
  被赋值为32768。
  maxSharedCapacityFactor:最终会被赋值为2。
  maxDelayedQueuesPerThread:被赋值为CPU核数2。
  我们再回到Stack的构造方法。
  根据Recycler初始化属性的逻辑,可以知道Stack中几个属性的值。
  maxCapacity:默认值为32768。
  ratioMask:默认值为7。
  maxDelayedQueues:默认值为CPU核数2。
  availableSharedCapacity:默认值是327682,也就是16384。1。2。2从Recycler中获取对象
  回顾上节Demo中的main()方法,从回收站获取对象。
  通过Recycler的get()方法获取对象,代码如下。
  首先判断maxCapacityPerThread是否为0,maxCapacityPerThread代表Stack最多能缓存多少个对象,如果缓存0个,说明对象将一个都不会回收。通过调用newObject创建一个对象,并传入一个NOOPHANDLE,NOOPHANDLE是一个Handle,我们看其定义。
  这里的recycle()方法是一个空实现,代表不进行任何对象回收。回到get()方法中,我们看第二步StackstackthreadLocal。get(),这里通过FastThreadLocal对象获取当前线程的Stack。获取Stack之后,从Stack中pop出一个Handle,其作用稍后分析。如果取出的对象为null,说明当前回收站内没有任何对象,通常第一次执行到这里对象还没回收,就会是null,这样则会通过stack。newHandle()创建一个Handle。创建出来的Handle的Value属性,通过重写的newObject()方法进行赋值,也就是Demo中的User。跟进newHandle()方法。
  这里创建一个DefaultHandle对象,并传入this,这里的this是当前Stack。DefaultHandle的构造方法的代码如下。
  这里初始化了stack属性。DefaultHandle中还有一个Value的成员变量。
  这里的Value用来绑定回收的对象本身。回到get()方法中,分析Handle,我们回到上一步。
  我们分析从Stack中弹出一个Handle的逻辑,跟进pop()方法。
  首先获取size,size表示当前Stack的对象数。如果size为0,则调用scavenge()方法,这个方法是异线程回收对象的方法,我们放在之后的小节进行分析。如果size大于0,则size进行自减,代表取出一个元素,然后通过size的数组下标的方式将Handle取出,之后将当前下标设置为null,最后将属性recycleId、lastRecycledId、size进行赋值。recycleId和lastRecycledId会在之后的小节进行分析,回到get()方法。
  无论是从Stack中弹出的Handle,还是创建的Handle,最后都要通过handle。value获取实际使用的对象。1。2。3相同线程内的对象回收
  上节中剖析了从Recycler中获取一个对象,本节分析在创建和回收是同线程的前提下,Recycler是如何进行回收的。回顾前面章节Demo中的main()方法。
  这是一个同线程回收对象的典型场景,在一个线程中将对象创建并且回收,我们的User对象定义了recycle方法。
  这里的recycle是通过Handle对象的recycle()方法实现对象回收的,实际调用的是DefaultHandle的recycle()方法。跟进recycle()方法。
  如果回收的对象为null,则抛出异常。如果不为null,则通过自身绑定Stack的push()方法将自身push到Stack中。push()方法的代码如下。
  首先判断当前线程和创建Stack的时候保存的线程是否是同一线程。如果是,说明是同线程回收对象,则执行pushNow()方法将对象放入Stack中。pushNow()方法的代码如下。
  如果第一次回收,item。recycleId和item。lastRecycledId都为0,则不会进入if块。继续往下看,item。recycleIditem。lastRecycledIdOWNTHREADID这一步将Handle的recycleId和lastRecycledId赋值为OWNTHREADID,OWNTHREADID在每一个recycle中都是唯一固定的,这里我们只需要记住这个概念就行。然后获取当前size,如果size超过上限大小,则直接返回。这里还有个判断dropHandle。
  if(!handle。hasBeenRecycled)表示当前对象之前是否没有被回收过,如果是第一次回收,会返回true,然后进入if。再看if中的判断if((handleRecycleCountratioMask)!0),handleRecycleCount表示当前位置Stack回收了多少次对象(回收了多少次,不代表回收了多少个对象,因为不是每次回收都会被成功地保存在Stack中),我们之前分析过ratioMask是7,这里(handleRecycleCountratioMask)!0表示回收的对象数如果不是8的倍数,则返回true,表示只回收18的对象,然后将hasBeenRecycled设置为true,表示已经被回收。回到pushNow()方法中,如果size的大小等于Stack中的数组Elements的大小,则将数组Elements进行扩容,最后size通过数组下标的方式将当前Handle设置到Elements的元素中,并将size进行自增。1。2。4不同线程间的对象回收
  异线程回收对象,就是创建对象和回收对象不在同一条线程的情况下对象回收的逻辑。在1。2。1节简单介绍过,异线程回收对象,是不会放在当前线程的Stack中的,而是放在一个WeakOrderQueue的数据结构中,回顾我们之前的示意图如下图所示。
  相关的逻辑,我们跟到源码中,首先从回收对象的入口方法开始。DefaultHandle的recycle()方法代码如下。
  继续看push()方法的代码。
  上节分析过,同线程会执行到pushNow(),有关具体逻辑也进行了分析。如果不是同线程,则会执行到pushLater()方法,传入Handle对象和当前线程对象,跟进pushLater()方法。
  首先通过DELAYEDRECYCLED。get()方法获取一个delayedRecycled对象,我们看DELAYEDRECYCLED的代码。
  我们看到DELAYEDRECYCLED是一个FastThreadLocal对象,initialValue()方法创建一个WeakHashMap对象,WeakHashMap是一个Map,Key为Stack,Value为前面提到过的WeakOrderQueue。从中可以分析到,每个线程都维护了一个WeakHashMap对象。WeakHashMap中的元素,是一个Stack和WeakOrderQueue的映射,说明不同的Stack对应不同的WeakOrderQueue。这里的映射关系可以举例说明。
  比如线程1创建了一个对象,在线程3进行了回收;线程2创建了一个对象,同样也在线程3进行了回收,那么线程3对应的WeakHashMap中保存了两组关系:线程1对应的Stack和WeakOrderQueue,以及线程2对应的Stack和WeakOrderQueue。我们回到pushLater()方法中,继续往下看。
  获取了当前线程的WeakHashMap对象delayedRecycled之后,通过delayedRecycled创建对象的线程的Stack,获取WeakOrderQueue。这里的this,就是创建对象的那个线程所属的Stack,这个Stack是绑定在Handle中的,在创建Handle对象的时候进行的绑定。假设当前线程是线程2,创建Handle的线程是线程1,通过Handle的Stack获取线程1的WeakOrderQueue。if(queuenull)说明线程2没有回收过线程1的对象,则进入if块的逻辑,首先判断if(delayedRecycled。size()maxDelayedQueues)。
  delayedRecycled。size()表示当前线程回收其他创建对象的线程的个数,也就是有几个其他的线程在当前线程回收对象。
  maxDelayedQueues表示最多能回收的线程个数,如果超过这个值,就表示当前线程不能再回收其他线程的对象了。
  通过delayedRecycled。put(this,WeakOrderQueue。DUMMY)标识创建对象的线程的Stack所对应的WeakOrderQueue不可用,DUMMY可以理解为不可用。如果没有超过maxDelayedQueues,则通过if判断中的WeakOrderQueue。allocate(this,thread)方式创建一个WeakOrderQueue。allocate传入this,也就是创建对象的线程对应的Stack,跟进allocate()方法。
  reserveSpace(stack。availableSharedCapacity,LINKCAPACITY)表示线程1的Stack还能不能分配LINKCAPACITY个元素,如果可以,则直接通过new的方式创建一个WeakOrderQueue对象。回到reserveSpace()方法。
  参数availableSharedCapacity表示线程1的Stack允许外部线程给其缓存多少个对象,之前我们分析过是16384,space默认是16。方法中通过一个CAS操作,将16384减去16,表示Stack可以给其他线程缓存的对象数为1638416,而这16个元素,将由线程2缓存。
  回到pushLater()方法,创建之后通过delayedRecycled。put(this,queue)将Stack和WeakOrderQueue进行关联,通过queue。add(item)将创建的WeakOrderQueue添加一个Handle。讲解WeakOrderQueue之前,先了解下WeakOrderQueue的数据结构。WeakOrderQueue维护了多个Link,Link之间通过链表进行连接,每个Link可以盛放16个Handle。我们分析过,在reserveSpace()方法中将stack。availableSharedCapacity16,其实就表示先分配16个空间放在Link里,下次回收的时候,如果这16个空间没有填满,则可以继续往里盛放。如果16个空间都已填满,则通过继续添加Link的方式继续分配16个空间用于盛放Handle。WeakOrderQueue和WeakOrderQueue之间也通过链表进行关联,可以根据下图理解上述逻辑。
  根据以上思路,我们看WeakOrderQueue的构造方法。
  这里有个Head和Tail,都指向一个Link对象,其实在WeakOrderQueue中维护了一个链表,Head和Tail分别代表头节点和尾节点,初始状态下,头节点和尾节点都指向同一个节点。简单看下Link的类的定义。
  每次创建一个Link,都会创建一个DefaultHandle类型的数组用于盛放DefaultHandle对象,默认大小是16个。
  readIndex是一个读指针,之后小节会进行分析。next节点则指向下一个Link。回到WeakOrderQueue的构造方法中,owner是对当前线程进行一个包装,代表了当前线程。
  接下来在一个同步块中,将当前创建的WeakOrderQueue插入Stack指向的第一个WeakOrderQueue,也就是Stack的Head属性,指向我们创建的WeakOrderQueue,如下图所示。
  如果线程2创建一个和Stack关联的WeakOrderQueue,Stack的头节点就会指向线程2创建的WeakOrderQueue。如果之后线程3也创建了一个和Stack关联的WeakOrderQueue,Stack的头节点就会指向新创建的线程3的WeakOrderQueue,然后线程3的WeakOrderQueue再指向线程2的WeakOrderQueue。也就是无论哪个线程创建一个和同一个Stack关联的WeakOrderQueue的时候,都插入Stack指向的WeakOrderQueue列表的头部,这样就可以将Stack和其他线程释放对象的容器WeakOrderQueue进行绑定。回到pushLater()方法。
  根据之前分析的WeakOrderQueue的数据结构,分析最后一步,也就是WeakOrderQueue的add()方法。
  首先看handle。lastRecycledIdid,lastRecycledId表示Handle上次回收的id,而id表示WeakOrderQueue的id,weakOrderQueue每次创建的时候,会自增一个唯一的id。Linktailthis。tail表示获取当前WeakOrderQueue中指向最后一个Link的指针,也就是尾指针。再看if((writeIndextail。get())LINKCAPACITY),tail。get()表示获取当前Link中已经填充元素的个数,如果等于16,说明元素已经填充满。然后通过reserveSpace()方法判断当前WeakOrderQueue是否还能缓存Stack的对象,reserveSpace()方法会根据Stack的属性availableSharedCapacity16的方式判断还能否缓存Stack的对象,如果不能再缓存Stack的对象,则返回。如果还能继续缓存,则再创建一个Link,并将尾节点指向新创建的Link,并且原来尾节点的next节点指向新创建的Link,然后获取当前Link的writeIndex,也就是写指针。如果新创建的Link中没有元素,writeIndex为0,之后将尾部的Link的Elements属性,也就是一个DefaultHandle类型的数组,通过数组下标的方式将第writeIndex个节点赋值为要回收的Handle,然后将Handle的Stack属性设置为null,表示当前Handle不是通过Stack进行回收的,最后将Tail节点的元素个数进行1,表示下一次将从writeIndex1的位置往里写。1。2。5获取不同线程间释放的对象
  上节分析了异线程回收对象,原理是通过与Stack关联的WeakOrderQueue进行回收。如果对象经过异线程回收之后,当前线程需要取出对象进行二次利用,当前Stack为空,则会通过当前Stack关联的WeakOrderQueue进行取出,这也是本节要分析的获取异线程释放对象的内容。
  在介绍之前,先看Stack类中的两个属性。
  这里的cursor、prev和head都是指向链表中WeakOrderQueue的指针,其中head指向最近创建的与Stack关联的WeakOrderQueue,也就是头节点。cursor代表的是寻找当前WeakOrderQueue,prev则是cursor的上一个节点,如下图所示。
  我们从获取对象的入口方法Handle的get()方法开始分析。
  这块逻辑我们并不陌生,Stack对象通过pop()方法弹出一个Handle,跟进pop()方法。
  这里重点关注的是,如果size为空,也就是当前Stack为空的情况下,会执行到scavenge()方法。这个方法就是从WeakOrderQueue获取对象的方法。跟进scavenge()方法。
  scavengeSome()方法表示如果已经回收到了对象,则直接返回;如果没有回收到对象,则将prev和cursor两个指针进行重置。继续跟进scavengeSome()方法。
  首先获取cursor指针,cursor指针代表要回收的WeakOrderQueue。如果cursor为空,则让其指向头节点,如果头节点也为空,说明当前Stack没有与其关联的WeakOrderQueue,则返回false。通过一个布尔值success标记回收状态,然后获取prev指针,也就是cursor的上一个节点,之后进入一个dowhile循环。dowhile循环的终止条件是没有遍历到最后一个节点并且回收的状态为false。我们仔细来看dowhile循环中的代码逻辑,首先cursor指针会调用transfer()方法,该方法表示从当前指针指向的WeakOrderQueue中将元素放入当前Stack中,如果取出成功则将success设置为true并跳出循环,transfer()方法我们稍后分析。
  继续往下看,如果没有获得元素,则会通过next属性获取下一个WeakOrderQueue,然后进入一个判断if(cursor。owner。get()null)。owner属性是与当前WeakOrderQueue关联的一个线程,get()方法获得关联的线程对象,如果这个对象为null说明该线程不存在,则进入if块,也就是一些清理的工作。if块中又进入一个判断if(cursor。hasFinalData()),表示当前的WeakOrderQueue中是否还有数据,如果有数据则通过for循环将数据通过transfer()方法传输到当前Stack中,传输成功的,将success标记为true。Transfer()方法是将WeakOrderQueue中一个Link中的Handle往Stack传输,这里通过for循环将每个Link中的数据都传输到Stack中。
  继续往下看,如果prev节点不为空,则通过prev。nextnext将cursor节点进行释放,也就是prev的下一个节点指向cursor的下一个节点。继续往下看else块中的prevcursor,表示如果当前线程还在,则将prev赋值为cursor,代表prev后移一个节点,最后通过cursornext将cursor后移一位,然后继续进行循环。循环结束之后,将Stack的prev和cursor属性进行保存。我们跟到transfer()方法中,分析如何将WeakOrderQueue中的Handle传输到Stack中。
  剖析之前,我们回顾WeakOrderQueue的数据结构,如下图所示。
  我们上节分析过,WeakOrderQueue是由多个Link组成的,每个Link通过链表的方式进行关联,其中Head属性指向第一个Link,Tail属性指向最后一个Link。在每个Link中有多个Handle,Link中维护了一个读指针readIndex,标识着读取Link中Handle的位置。继续分析transfer()方法,首先获取头节点,并判断头节点是否为空,如果头节点为空,说明当前WeakOrderQueue并没有Link,返回false。if(head。readIndexLINKCAPACITY)判断读指针是否为16,因为Link中元素最大数量就是16,所以如果读指针为16,说明当前Link中的数据都被取走了。接着判断head。nextnull,表示是否还有下一个Link,如果没有,则说明当前WeakOrderQueue没有元素了,返回false。如果当前Head的下一个节点不为null,则将当前头节点指向下一个节点,将原来的头节点进行释放,其移动关系如下图所示。
  继续往下看,获取头节点的读指针和Head中元素的数量,计算可以传输元素的大小,如果大小为0,则返回false,如下图所示。
  接着,获取当前Stack的大小,当前Stack大小加上可以传输的大小表示Stack中所需要的容量。if(expectedCapacitydst。elements。length)表示如果需要的容量大于当前Stack中所维护的数组的大小,则将Stack中维护的数组进行扩容,进入if块中。扩容之后会返回actualCapacity,表示扩容之后的大小。再看srcEndmin(srcStartactualCapacitydstSize,srcEnd)这步,srcEnd表示可以从Link中获取的最后一个元素的下标。
  这里对srcStartactualCapacitydstSize进行拆分,actualCapacitydstSize表示扩容后的大小原Stack的大小,也就是最多能往Stack中传输多少元素。读指针可以往Stack传输的数量和,表示往Stack中传输的最后一个下标,这里的下标和srcEnd中取一个较小的值,也就是既不能超过Stack的容量,也不能造成当前Link中下标越界。
  继续往下看,intnewDstSizedstSize表示初始化Stack的下标,表示Stack中从这个下标开始添加数据。然后判断srcStart!srcEnd,表示能不能从Link中获取内容,如果不能,则返回false,如果可以,则进入if块中,接着获取当前Link的数组Elements和Stack中的数组Elements,通过for循环,以数组下标的方式不断将当前Link中的数据放入Stack中,for循环中首先获取Link的第i个元素,接下来关注一个细节。
  这里element。recycleId0表示对象没有被回收过,则赋值为lastRecycledId,lastRecycledId是WeakOrderQueue中的唯一下标,通过赋值标记Element被回收过,然后继续判断element。recycleId!element。lastRecycledId,表示该对象被回收过,但是回收的recycleId却不是最后一次回收lastRecycledId,这是一种异常情况,表示一个对象在不同的地方被回收过两次,这种情况则抛出异常,接着将Link的第i个元素设置为null,继续往下看。
  这里表示控制回收站回收的频率,之前的章节中分析过,这里不再赘述。
  element。stackdst表示将Handle的Stack属性设置到当前Stack。
  dstElems〔newDstSize〕element表示通过数组下标的方式将Link中的Handle赋值到Stack的数组中。
  继续往下看:
  这里的if表示循环结束后,如果Link中的数据已经回收完毕,并且还有下一个节点则会进到reclaimSpace()方法。跟到reclaimSpace()方法。
  将availableSharedCapacity加上16,表示WeakOrderQueue还可以继续插入Link。继续看transfer()方法。
  this。headhead。next表示将头节点后移一个元素。
  head。readIndexsrcEnd表示将读指针指向srcEnd,下一次读取可以从srcEnd开始。
  if(dst。sizenewDstSize)表示没有向Stack传输任何对象,则返回false,否则就通过dst。sizenewDstSize更新Stack的大小为newDstSize,并返回true。
  以上就是从Link中往Stack中传输数据的过程。
投诉 评论 转载

用无可辩驳的事实,揭露地域闹粉的四点之恶一,地域闹粉之恶归纳起来主要有四点:第一,恶意捧杀,语言贿赂,无底线吹捧。例如:堆砌并用尽吹捧词,恶意冠名李盈莹、姚迪,并进行恬不知耻的、登峰造极的、无底线的捧杀和……知道如何让瓜分金额失而复得吗?掌握这点很关键因为热爱,所以相信,我一直都坚信头条是公平、公正的,为什么这样说呢,因为我深有体会。在前段时间反馈的新能源新知计划的遗漏瓜分金额38。52元昨天已经补发下来了,没想到会这……来中国打工的朝鲜女子越来越多,是因为,他们能够赚到钱又有面子中国这几年的发展真的是越来越好了,这些除了中国人有明显的感觉以外,就连外国人他们也对中国特别的佩服,每一年来到中国旅游,甚至来中国打工的人都特别的多。比如有许多的朝鲜女孩子他们……高并发编程之Netty高性能调优工具类解析1。1多线程共享FastThreadLocal我们在剖析堆外内存分配的时候简单介绍过FastThreadLocal,它类似于JDK的ThreadLocal,也是用于多线程……雄安智能网联巴士进行道路测试!来看聪明的车与智慧的路在雄安相2月22日,智能网联巴士901线测试车辆与普通城市公交车驶出公交站台。雄安新区的智能网联巴士近期正在进行投入运营前的道路测试,该巴士配备了超高感度摄像头、超声波雷达、激光雷达、……倾心呵护东江源头水2月6日,香港同胞饮用水的供给源江西省安远县东江源头三百山绿化地带一派欢欣景象。200多名群众在县林业部门组织下,开展回归自然、放飞心情新春植树绿化系列活动。1200余株刚种下……父亲用残忍的手段虐待女儿,只因女孩的眼泪能化为黄金,人性漫画最近,美国俄亥俄州发生了危险化学物氯乙烯泄露事件。氯乙烯燃烧后会产生有毒的光气和氯化氢。光气具有较高毒性,会导致人呕吐和呼吸困难,曾被作为化学武器用于第一次世界大战。目前事发地……如果孕妇有炎症怎么办?在孕期期间,不免会有一些孕妇不注重保健,导致患上了妇科疾病!专家指出,孕妇由于体制原因激素水平会升高,同时分泌物也会增加,阴道酸碱度也会随之改变,寄生于在阴道区域的细菌也随着环……江西日报新时代新征程新伟业专栏报道萍乡坚定不移推进工业强市来源:【江西日报江西新闻客户端】原标题:精准施策久久为功萍乡坚定不移推进工业强市江西新闻客户端萍乡讯(江西日报全媒体记者尹晓军、刘启红)日前,萍乡市今年……CBA全明星赛北区队加时击败南区队阿不都沙拉木荣膺MVP新华社厦门3月26日电(记者郑直、颜之宏)2023CBA全明星周末正赛26日晚在厦门上演,经过加时鏖战,北区明星队117:113击败南区明星队,队中阿不都沙拉木获评本场比赛最有……首届学青会会徽吉祥物主题口号官方宣传片正式发布!来源:南宁发布3月22日,第一届全国学生(青年)运动会(简称学青会)新闻发布会在广西南宁举办。发布会上,学青会会徽、吉祥物、主题口号、宣传片等逐一亮相发布。发布会现……安全宣传教育怎么抓?广州有一套!有全国第一所应急安全教育特色公办全日制幼儿园,率先制定进社区进农村进家庭安全宣传工作验收标准,对近两年来发生生产安全事故或存在突出隐患企业的负责人和安全管理人员开展相应培训近年……
人鱼公主火爆!1个月,110万人次!安全管理造句用安全管理造句大全撕嘴皮的孩子不怕痛吗?小动作中藏着许多原因,家长了解几个嘉定三屠清军三次屠杀嘉定百姓就为推行剃发令销量对权重有多大影响陶菲格哈基姆生活就是目标生活就是意志原文及赏析适合穿搭博主的一双AJ1球鞋防护林带小气候有哪些特点以为是长辈,其实是同岁的5对男星,差别实在是太大了!回老家斗地主微信网名经典女生优雅

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找