Netty源码分析第八章: 高性能工具类FastThreadLocal和Recyclerhtml
第七节: 获取异线程释放的对象数组
上一小节分析了异线程回收对象, 原理是经过与stack关联的WeakOrderQueue进行回收性能优化
若是对象通过异线程回收以后, 当前线程须要取出对象进行二次利用, 若是当前stack中为空, 则会经过当前stack关联的WeakOrderQueue进行取出, 这也是这一小写要分析的, 获取异线程释放的对象数据结构
在介绍以前咱们首先看Stack类中的两个属性:工具
private WeakOrderQueue cursor, prev; private volatile WeakOrderQueue head;
这里都是指向WeakOrderQueue的指针, 其中head咱们上一小节分析过, 指向最近建立的和stack关联WeakOrderQueue, 也就是头结点源码分析
cursor表明的是寻找的当前WeakOrderQueue, pre则是cursor上一个节点, 如图所示:性能
8-7-1优化
咱们从获取对象的入口方法, handle的get开始分析:this
public final T get() { if (maxCapacityPerThread == 0) { return newObject((Handle<T>) NOOP_HANDLE); } Stack<T> stack = threadLocal.get(); DefaultHandle<T> handle = stack.pop(); if (handle == null) { handle = stack.newHandle(); handle.value = newObject(handle); } return (T) handle.value; }
这块逻辑咱们并不陌上, stack对象经过pop弹出一个handlespa
咱们跟到pop方法中:
DefaultHandle<T> pop() { int size = this.size; if (size == 0) { if (!scavenge()) { return null; } size = this.size; } size --; DefaultHandle ret = elements[size]; elements[size] = null; if (ret.lastRecycledId != ret.recycleId) { throw new IllegalStateException("recycled multiple times"); } ret.recycleId = 0; ret.lastRecycledId = 0; this.size = size; return ret; }
这里咱们重点关注, 若是size为空, 也就是当前tack为空的状况下, 会走到scavenge方法, 这个方法, 就是从WeakOrderQueue获取对象的方法
跟进scavenge方法:
boolean scavenge() { if (scavengeSome()) { return true; } prev = null; cursor = head; return false; }
scavengeSome方法表示已经回收到了对象, 则直接返回, 若是没有回收到对象, 则将prev和cursor两个指针进行重置
继续跟到scavengeSome方法中:
boolean scavengeSome() { WeakOrderQueue cursor = this.cursor; if (cursor == null) { cursor = head; if (cursor == null) { return false; } } boolean success = false; WeakOrderQueue prev = this.prev; do { if (cursor.transfer(this)) { success = true; break; } WeakOrderQueue next = cursor.next; if (cursor.owner.get() == null) { if (cursor.hasFinalData()) { for (;;) { if (cursor.transfer(this)) { success = true; } else { break; } } } if (prev != null) { prev.next = next; } } else { prev = cursor; } cursor = next; } while (cursor != null && !success); this.prev = prev; this.cursor = cursor; return success; }
首先拿到cursor指针, cursor指针表明要回收的WeakOrderQueue
若是cursor为空, 则让其指向头节点, 若是头节点也空, 说明当前stack没有与其关联的WeakOrderQueue, 则返回false
经过一个布尔值success标记回收状态
而后拿到pre指针, 也就是cursor的上一个节点, 以后进入一个do-while循环
do-while循环的终止条件是, 若是没有遍历到最后一个节点而且回收的状态为false, 这里咱们能够分析到再循环体里, 是无论遍历与stack关联的WeakOrderQueue, 直到弹出对象为止
跟到do-while循环中:
首先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进行传输, 有关link的相关内容, 咱们上一小节也进行过度析
因此这里经过for循环将每一个link的中的数据传输到stack中
继续往下看, 若是pre节点不为空, 则经过 prev.next = next 将cursor节点进行释放, 也就是pre的下一个节点指向cursor的下一个节点
继续往下看else块中的 prev = cursor
这里表示若是当前线程还在, 则将prev赋值为cursor, 表明prev后移一个节点
最后经过cursor = next将cursor后移一位, 而后再继续进行循环
循环结束以后, 将stack的prev和cursor属性进行保存
咱们跟到transfer方法中, 分析如何将WeakOrderQueue中的handle传输到stack中:
boolean transfer(Stack<?> dst) { Link head = this.head; if (head == null) { return false; } if (head.readIndex == LINK_CAPACITY) { if (head.next == null) { return false; } this.head = head = head.next; } final int srcStart = head.readIndex; int srcEnd = head.get(); final int srcSize = srcEnd - srcStart; if (srcSize == 0) { return false; } final int dstSize = dst.size; final int expectedCapacity = dstSize + srcSize; if (expectedCapacity > dst.elements.length) { final int actualCapacity = dst.increaseCapacity(expectedCapacity); srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd); } if (srcStart != srcEnd) { final DefaultHandle[] srcElems = head.elements; final DefaultHandle[] dstElems = dst.elements; int newDstSize = dstSize; for (int i = srcStart; i < srcEnd; i++) { DefaultHandle element = srcElems[i]; if (element.recycleId == 0) { element.recycleId = element.lastRecycledId; } else if (element.recycleId != element.lastRecycledId) { throw new IllegalStateException("recycled already"); } srcElems[i] = null; if (dst.dropHandle(element)) { continue; } element.stack = dst; dstElems[newDstSize ++] = element; } if (srcEnd == LINK_CAPACITY && head.next != null) { reclaimSpace(LINK_CAPACITY); this.head = head.next; } head.readIndex = srcEnd; if (dst.size == newDstSize) { return false; } dst.size = newDstSize; return true; } else { return false; } }
剖析以前这里咱们回顾WeakOrderQueue的数据结构, 如图所示:
8-7-2
咱们上一小节分析过, WeakOrderQueue是由多个link组成, 每一个link经过链表的方式进行关联, 其中head属性指向第一个link, tail属性指向最后一个link
在每一个link中有多个handle
在link中维护了一个读指针readIndex, 标记着读取link中handle的位置
咱们继续分析transfer方法:
首先获取头结点, 并判断头结点是否为空, 若是头结点为空, 说明当前WeakOrderQueue并无link, 返回false
if (head.readIndex == LINK_CAPACITY) 这里判断读指针是否为16, 由于link中元素最大数量就是16, 若是读指针为16, 说明当前link中的数据都被取走了
接着判断 head.next == null , 表示是否还有下一个link, 若是没有下一个link, 则说明当前WeakOrderQueue没有元素了, 则返回false
若是当前head的next节点不为null, 则将当前head节点指向下一个节点, 将原来的head节点进行释放, 移动关系如图所示:
8-7-3
继续往下看, 拿到head节点的读指针和head中元素的数量, 接着计算能够传输元素的大小, 若是大小为0, 则返回false
8-7-4
接着, 拿到当前stack的大小, 当前stack大小加上能够传输的大小表示stack中所须要的容量
if (expectedCapacity > dst.elements.length) 表示若是须要的容量大于当前stack中所维护的数组的大小, 则将stack中维护的数组进行扩容, 进入if块中
扩容以后会返回actualCapacity, 表示扩容以后的大小
再看 srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd) 这步
srcEnd表示能够从Link中取的最后一个元素的下标
srcStart + actualCapacity - dstSize 这里咱们进行一个拆分, actualCapacity - dstSize表示扩容后大大小-原stack的大小, 也就是最多能往stack中传输多少元素
读指针+能够往stack传输的数量, 能够表示往stack中传输的最后一个下标, 这里的下标和srcEnd中取一个较小的值, 也就是既不能超过stack的容量, 也不能形成当前link中下标越界
继续往下看
int newDstSize = dstSize 表示初始化stack的下标, 表示stack中从这个下标开始添加数据
而后判断 srcStart != srcEnd , 表示能不能同link中获取内容, 若是不能, 则返回false, 若是能够, 则进入if块中
接着拿到当前link的数组elements和stack中的数组elements
而后经过for循环, 经过数组下标的方式不断的将当前link中的数据放入到stack中
for循环中首先拿到link的第i个元素
接着咱们咱们关注一个细节:
if (element.recycleId == 0) { element.recycleId = element.lastRecycledId; } else if (element.recycleId != element.lastRecycledId) { throw new IllegalStateException("recycled already"); }
这里 element.recycleId == 0 表示对象没有被回收过, 若是没有被回收过, 则赋值为lastRecycledId, 咱们前面分析过lastRecycledId是WeakOrderQueue中的惟一下标, 经过赋值标记element被回收过
而后继续判断 element.recycleId != element.lastRecycledId , 这表示该对象被回收过, 可是回收的recycleId却不是最后一次回收lastRecycledId, 这是一种异常状况, 表示一个对象在不一样的地方被回收过两次, 这种状况则抛出异常
接着将link的第i个元素设置为null
继续往下看:
if (dst.dropHandle(element)) { continue; }
这里表示控制回收站回收的频率, 以前的小节咱们分析过, 这里再也不赘述
element.stack = dst 表示将handle的stack属性设置到当前stack
dstElems[newDstSize ++] = element 这里经过数组的下标的方式将link中的handle赋值到stack的数组中
继续往下看:
if (srcEnd == LINK_CAPACITY && head.next != null) { reclaimSpace(LINK_CAPACITY); this.head = head.next; }
这里的if表循环结束后, 若是link中的数据已经回收完毕, 而且还有下一个节点则会进到reclaimSpace方法
咱们跟到reclaimSpace方法:
private void reclaimSpace(int space) { assert space >= 0; availableSharedCapacity.addAndGet(space); }
这里将availableSharedCapacity加上16, 表示WeakOrderQueue还能够继续插入link
继续看transfer方法:
this.head = head.next 表示将head节点后移一个元素
head.readIndex = srcEnd 表示将读指针指向srcEnd, 下一次读取能够从srcEnd开始
if (dst.size == newDstSize) 表示没有向stack传输任何对象, 则返回false
不然就经过 dst.size = newDstSize 更新stack的大小为newDstSize, 并返回true
以上就是从link中往stack中传输数据的过程
第八章总结
这一章主要讲解了两个性能优化工具了FastThreadLocal和Recycler
FastThreadLocal和jdk的ThreadLocal功能相似, 只是性能更快, 经过FastTreadLocalThread中的threadLocalMap对象, 经过数组下标的方式进行保存和获取对象
Recycler是一个轻量级的对象回收站, 用于对象重用, 避免了对象的频繁建立和减轻gc的压力
Recycler同线程回收对象是经过一个线程共享的stack实现的, 将对象包装成handle并存入stack中
Reclyer异线程回收对象是将handle存入一个与stack关联的WeakOrderQueue中, 同一个stack中关联的不一样WeakOrderQueue由不一样的线程建立
从Recycler获取对象时stack中有值, 则能够直接从stack中获取
若是stack中没有值则经过stack关联的WeakOrderQueue中获取