Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler
第七节: 获取异线程释放的对象
上一小节分析了异线程回收对象, 原理是通过与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开始分析:
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弹出一个handle
我们跟到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中获取
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第7节: 获取异线程释放的对象的更多相关文章
-
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->;第4节: recycler中获取对象
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第四节: recycler中获取对象 这一小节剖析如何从对象回收站中获取对象: 我们回顾上一小节demo的ma ...
-
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->;第5节: 同线程回收对象
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第五节: 同线程回收对象 上一小节剖析了从recycler中获取一个对象, 这一小节分析在创建和回收是同线程的 ...
-
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->;第6节: 异线程回收对象
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第六节: 异线程回收对象 异线程回收对象, 就是创建对象和回收对象不在同一条线程的情况下, 对象回收的逻辑 我 ...
-
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->;第1节: FastThreadLocal的使用和创建
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 概述: FastThreadLocal我们在剖析堆外内存分配的时候简单介绍过, 它类似于JDK的ThreadL ...
-
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->;第2节: FastThreadLocal的set方法
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第二节: FastThreadLocal的set方法 上一小节我们学习了FastThreadLocal的创建和 ...
-
Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->;第3节: recycler的使用和创建
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第三节: recycler的使用和创建 这一小节开始学习recycler相关的知识, recycler是n ...
-
Netty源码分析第3章(客户端接入流程)---->;第1节: 初始化NioSockectChannelConfig
Netty源码分析第三章: 客户端接入流程 概述: 之前的章节学习了server启动以及eventLoop相关的逻辑, eventLoop轮询到客户端接入事件之后是如何处理的?这一章我们循序渐进, 带 ...
-
Netty源码分析第3章(客户端接入流程)---->;第2节: 处理接入事件之handle的创建
Netty源码分析第三章: 客户端接入流程 第二节: 处理接入事件之handle的创建 上一小节我们剖析完成了与channel绑定的ChannelConfig初始化相关的流程, 这一小节继续剖析客户端 ...
-
Netty源码分析第3章(客户端接入流程)---->;第3节: NioSocketChannel的创建
Netty源码分析第三章: 客户端接入流程 第三节: NioSocketChannel的创建 回到上一小节的read()方法: public void read() { //必须是NioEventLo ...
随机推荐
-
密码学应用(DES,AES, MD5, SHA1, RSA, Salt, Pkcs8)
目录 一.数据加密标准 - Data Encryption Standard(DES) 二.高级加密标准 - Advanced Encryption Standard(AES) 三.消息摘要算法第五版 ...
-
java代码走查审查规范
分类 重要性 检查项 备注 命名 重要 命名规则是否与所采用的规范保持一致? 成员变量,方法参数等需要使用首字母小写,其余单词首字母大写的命名方式,禁止使用下划线(_)数字等方式命名不 ...
-
hdu1521 排列组合(指数型母函数)
题意: 有n种物品,并且知道每种物品的数量ki.要求从中选出m件物品的排数. (全题文末) 知识点: 普通母函数 指数型母函数:(用来求解多重集的排列问题) n个元素,其中a1,a2, ...
-
SQL Server安全(10/11):行级别安全(Row-Level Security)
在保密你的服务器和数据,防备当前复杂的攻击,SQL Server有你需要的一切.但在你能有效使用这些安全功能前,你需要理解你面对的威胁和一些基本的安全概念.这篇文章提供了基础,因此你可以对SQL Se ...
-
super用法和继承中的构造方法
当new出来一个对象的时候, this是只想对象本身. 在存在继承关系时, 在子类中用super表示引用父类中的东西. 子类的构造过程必须调用父类的构造方法. 子类中包含父类,所以子类中一定要先调用 ...
-
ABAP程序执行效率和优化 ABAP Performance Examples
一. SQL Interface1. Select ... Where vs. Select + Check用Select … Where语句效率比Select ...
-
Windows远程桌面相关
3389端口是Windows系统远程终端服务以及系统远程桌面服务默认所使用的端口,原本是为了管理人员能够方便的远程维护管理计算机而设计的,但如今已经成为了黑客们最喜爱的一种人侵途径,入侵者通过3389 ...
-
html小知识点(220-1)
<input type="file" multiple="multiple"><form> <input type="t ...
-
RTX二次开发SDK需要注意的地方
1.如果是ASP.net二次开发调用的,线程池必须开发 Enable 32-bit Applications设置为true. 因为RTX的调用接口很多是32位的.否则会报一下错误 RootObj = ...
-
原生JavaScript技巧大收集(1~10)
1.原生JavaScript实现字符串长度截取 01 function cutstr(str, len) { 02 var temp; 03 var icount = 0; 04 ...