RECYCLER.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;//返回值
}
private static final Handle NOOP_HANDLE = new Handle() {
@Override
public void recycle(Object object) {
// NOOP
}
};
首先通过线程本地变量获取Stack
,这里前面讲过FastThreadLocal
的处理了,就不多说了,如果没获取到,就会初始化一个,也就是:
stack.pop()
获取Stack
后,就开始调用pop
来获取。
DefaultHandle<T> pop() {
int size = this.size;
if (size == 0) {//没有了就清理尝试从队列中转过来
if (!scavenge()) {//清理队列失败
return null;
}
size = this.size;//设置个数
if (size <= 0) {//如果还是0的话就返回null
// double check, avoid races
return null;
}
}
size --;//个数-1
DefaultHandle ret = elements[size];//获取对象
elements[size] = null;//清空对象
this.size = size;//更新数量
if (ret.lastRecycledId != ret.recycleId) {
throw new IllegalStateException("recycled multiple times");
}
ret.recycleId = 0;
ret.lastRecycledId = 0;
return ret;
}
这里可以分两中情况,一种是Stack中没有对象的情况,另一种是有对象的情况。
Stack有对象的情况
有对象,就直接将size
减1
,size
其实就是一个索引,又能表示数量,默认是0
,放入一个加1
,取出一个减1
,这样就不需要去移动数组,效率高,然后按照size
索引取出elements
中的DefaultHandle
对象,设置对象的回收属性,返回 。这个是比较简单的情况。
Stack没有对象的情况
scavenge清理
进行WeakOrderQueue
的清理,也就是将WeakOrderQueue
里的对象转移到Stack
中。
private boolean scavenge() {
// continue an existing scavenge, if any
if (scavengeSome()) {//有清理一些链接了
return true;
}
// 没清理的就重新设置
prev = null;
cursor = head;
return false;
}
scavengeSome清理一些
确实像这个方法名一样,只是清理了一部分。
- 如果以前没有清理过或者没有要清理的了,
cursor
为null
,然后尝试开始从head
清理。如果head
也为null
,说明没有WeakOrderQueue
,直接返回false
清理失败,否则cursor
就是head
,即可以从head
开始清理。 - 如果以前有清理过,获取到
prev
,即上一个WeakOrderQueue
,便于后面删除结点保持链表不断链。
然后开始尝试将cursor
中的对象转移到Stack
中。
- 如果转移成功直接返回
true
。 - 如果发现
cursor
的引用线程不存在了,如果cursor
还有有对象的话,全部转移到Stack
中,并设置转移成功标志true
。如果prev
存在的话,就把cursor
空间释放,并且从链表中删除。 - 如果
cursor
的引用线程还存在,就把prev
指向cursor
。
最后cursor
指向下一个WeakOrderQueue
。
如果发现cursor
不为空,且没有转移成功过,就再进行转移,直到cursor
为空,或者转移成功为止。
最后设置prev
和cursor
。
private boolean scavengeSome() {
WeakOrderQueue prev;
WeakOrderQueue cursor = this.cursor;
if (cursor == null) {//游标为null
prev = null;
cursor = head;//游标指向头结点
if (cursor == null) {
return false;
}
} else {
prev = this.prev;
}
boolean success = false;
do {
if (cursor.transfer(this)) {//每次转移一个链接的量,由于有间隔,一般就只有2个转移
success = true;//转移成功
break;
}
WeakOrderQueue next = cursor.getNext();//只有上一个转移完了,才会获取下一个队列
if (cursor.get() == null) {//关联线程被回收为null了
if (cursor.hasFinalData()) {//还有对象
for (;;) {
if (cursor.transfer(this)) {//把队列中的所有链接全部转移完为止
success = true;
} else {
break;
}
}
}
if (prev != null) {//如果cursor的前一个队列prev存在
cursor.reclaimAllSpaceAndUnlink();//释放cursor结点空间
prev.setNext(next);//从单链表中删除cursor结点,prev的next指向cursor的下一个,第一个head是不释放的
}
} else {
prev = cursor;//prev保存前一个,用来链接删除结点的时候链接下一个结点,保持不断链
}
cursor = next;//游标指向下一个队列
} while (cursor != null && !success);//下一个队列不为空,且没有成功转移过
this.prev = prev;
this.cursor = cursor;//设置游标
return success;
}
简单的图示就是这样:
WeakOrderQueue的transfer转移
简单的来说就是从WeakOrderQueue
的head
中的链接link
开始遍历,把link
中的element
数组的所有对象转移给Stack
的element
数组。其中readIndex
表示下一个能转移的数组索引,如果readIndex=LINK_CAPACITY
即表示转移完了。
-
如果发现
link
已经转移完,又是最后一个link
,就直接返回false
,否则就把他的空间释放了,head
的link
指向下一个。差不多就是这个样子: -
之后还会判断一次,新获取的下一个
Link
是否有可以转移的对象,如果没有就直接返回false
了。 -
如果还能转移,就计算转换后的
Stack
中预期有多少对象,如果elements
不够放的话就进行扩容。如果扩容了还不行的话,说明满了,就返回false
了。 -
如果可以放的话,就开始转移,从
Link
的elements
转移到Stack
的elements
,也不是每一个都会转过去,这里也有个回收间隔,也是间隔8
个,也即所有16
个对象只能转2
个过去, 其实就是回收的比较少,大部分都是丢弃的 。如果这个Link
所有对象都转移完了,且他的下一个不为null
,就将head
的link
指向下一个。 -
最后判断是否有对象转移,如果有就给
Stack
设置新size
并返回true
,否则就false
, 因为转移有间隔,不一定能有对象转移过去的 。
boolean transfer(Stack<?> dst) {
Link head = this.head.link;//获取头链接
if (head == null) {//没有了
return false;
}
if (head.readIndex == LINK_CAPACITY) {//链接中的对象全部转移了
if (head.next == null) {//又是最后一个了,返回失败
return false;
}
head = head.next;//到下一个
this.head.relink(head);//头结点指向新的链接
}
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) {//检查有没被回收过,没有就是0
element.recycleId = element.lastRecycledId;
} else if (element.recycleId != element.lastRecycledId) {
throw new IllegalStateException("recycled already");
}
srcElems[i] = null;//清空引用,便于GC
if (dst.dropHandle(element)) {//如果不符合条件丢弃对象,并继续
// Drop the object.
continue;
}
element.stack = dst;//把栈设置回来,下次会从栈里里获取
dstElems[newDstSize ++] = element;//放入栈的数组里
}
//如果这个链接是满的,而且下一个不为空,那就把这个链接给回收了,单链表删除
if (srcEnd == LINK_CAPACITY && head.next != null) {
// Add capacity back as the Link is GCed.
this.head.relink(head.next);
}
head.readIndex = srcEnd;//设置获取完毕
if (dst.size == newDstSize) {//如果没有对象获取,就返回失败
return false;
}
dst.size = newDstSize;//有就设置个数,返回成功
return true;
} else {
// The destination stack is full already.
return false;//栈满了
}
}
Head的relink重链接到下一个
void relink(Link link) {
reclaimSpace(LINK_CAPACITY);//回收
this.link = link;//链接指向link
}
//回收空间
private void reclaimSpace(int space) {
availableSharedCapacity.addAndGet(space);
}
WeakOrderQueue的hasFinalData是否还有数据
这个很简单,即最后一个Link
是否还有可以转移的。
boolean hasFinalData() {
return tail.readIndex != tail.get();
}
WeakOrderQueue的reclaimAllSpaceAndUnlink释放所有空间,并从链表中删除
这个操作就是当所在的线程被回收了,所有的对象也释放了,但是因为有Stack的单链表还引用着,还不能释放,所以要释放剩余的Link
,并从单链表中删除。
void reclaimAllSpaceAndUnlink() {
head.reclaimAllSpaceAndUnlink();
this.next = null;
}
Head的reclaimAllSpaceAndUnlink释放所有空间
其实就是从head
的Link
开始,删除到最后,把空间回收了。
void reclaimAllSpaceAndUnlink() {
Link head = link;
link = null;
int reclaimSpace = 0;
while (head != null) {
reclaimSpace += LINK_CAPACITY;//回收空间
Link next = head.next;//指向下一个
head.next = null;
head = next;
}
if (reclaimSpace > 0) {
reclaimSpace(reclaimSpace);//回收
}
}
private void reclaimSpace(int space) {
availableSharedCapacity.addAndGet(space);
}
Stack的increaseCapacity扩容
扩容两倍,不超过最大限制,把数组元素都复制过去。
int increaseCapacity(int expectedCapacity) {
int newCapacity = elements.length;
int maxCapacity = this.maxCapacity;
do {
newCapacity <<= 1;
} while (newCapacity < expectedCapacity && newCapacity < maxCapacity);
newCapacity = min(newCapacity, maxCapacity);
if (newCapacity != elements.length) {
elements = Arrays.copyOf(elements, newCapacity);
}
return newCapacity;
}
至此,获取也基本讲完了,其他的可以自己去看看调试下,这样会理解的比较好。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。