吃透Netty源码系列三十三之Recycler细节解析二

自闭症网瘾萝莉.ら 提交于 2020-02-22 23:11:22

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

有对象,就直接将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;
        }

简单的图示就是这样:
在这里插入图片描述

WeakOrderQueue的transfer转移

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

  • 如果发现link已经转移完,又是最后一个link,就直接返回false,否则就把他的空间释放了,headlink指向下一个。差不多就是这个样子:
    在这里插入图片描述
  • 之后还会判断一次,新获取的下一个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;
        }

至此,获取也基本讲完了,其他的可以自己去看看调试下,这样会理解的比较好。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!