2023-09-13  阅读(6)
原文作者:https://blog.csdn.net/wangwei19871103/category_9681495_2.html 原文地址: https://blog.csdn.net/wangwei19871103/article/details/104446340

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的处理了,就不多说了,如果没获取到,就会初始化一个,也就是:

202309132205277781.png

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有对象的情况

有对象,就直接将size1size其实就是一个索引,又能表示数量,默认是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清理一些

确实像这个方法名一样,只是清理了一部分。

  • 如果以前没有清理过或者没有要清理的了,cursornull,然后尝试开始从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为空,或者转移成功为止。
最后设置prevcursor

     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;
            }

简单的图示就是这样:

202309132205281622.png

WeakOrderQueue的transfer转移

简单的来说就是从WeakOrderQueuehead中的链接link开始遍历,把link中的element数组的所有对象转移给Stackelement数组。其中readIndex表示下一个能转移的数组索引,如果readIndex=LINK_CAPACITY即表示转移完了。

  • 如果发现link已经转移完,又是最后一个link,就直接返回false,否则就把他的空间释放了,headlink指向下一个。差不多就是这个样子:

    202309132205288283.png

  • 之后还会判断一次,新获取的下一个Link是否有可以转移的对象,如果没有就直接返回false了。

  • 如果还能转移,就计算转换后的Stack中预期有多少对象,如果elements不够放的话就进行扩容。如果扩容了还不行的话,说明满了,就返回false了。

  • 如果可以放的话,就开始转移,从Linkelements转移到Stackelements,也不是每一个都会转过去,这里也有个回收间隔,也是间隔8个,也即所有16个对象只能转2个过去, 其实就是回收的比较少,大部分都是丢弃的 。如果这个Link所有对象都转移完了,且他的下一个不为null,就将headlink指向下一个。

  • 最后判断是否有对象转移,如果有就给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释放所有空间

其实就是从headLink开始,删除到最后,把空间回收了。

      			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] ,回复【面试题】 即可免费领取。

阅读全文