Netty的Recycler 大改版 4.174/5/6/7 版本新解

这次的Recycler 改动很大
去掉了之前用的stack,链表,weakReference等,详细请见 https://github.com/netty/netty/pull/11858

  • ok,Netty的Recycler 的设计原理,就是需要对象的时候取,不需要把对象存起来。
    启到复用的效果存起来,怎么存,取,从哪里取。做一个怎样的集合,要不要考虑线程安全。
  • 对,接下来就是结合新版本来解答下面这些问题。
    线程安全。获得新对象
  • 先简单的看下这段代码,里面有threadLocal 来为每个线程存放一个集合localPool。
    这里就是利用threadLocal来保障线程安全。
    在从localPool中获取Handle。这里的Handle有两个作用,一个是用来绑定新对象,一个是用来绑定localPool,目的是用绑定的localPool,存当前的Handle。
//Recycler类的get方法
    @SuppressWarnings("unchecked")
    public final T get() {
        if (maxCapacityPerThread == 0) {
            return newObject((Handle<T>) NOOP_HANDLE);
        }
        //从threadLocal 获取一个localPool
        LocalPool<T> localPool = threadLocal.get();
        //从localPool获取一个Handle。
        DefaultHandle<T> handle = localPool.claim();
        T obj;
        //如果handle是null
        if (handle == null) {
            // 创建新handle,并绑定此localPool。
            handle = localPool.newHandle();
            if (handle != null) {
                //创建新对象,对需要复用的对象。一般而言,新对象也会有个成员变量来存Handle,
                //目的是之后利用Handle的方法,将新对象绑定的Handle存到localPool中
                obj = newObject(handle);
                //绑定对象。
                handle.set(obj);
            } else {
                obj = newObject((Handle<T>) NOOP_HANDLE);
            }
        } else {
        //直接从hanle中获取value
            obj = handle.get();
        }

        return obj;
    }
  • 再来看存,刚刚再上段代码就写到了,存,是通过handle来存的,没错。
    就是简单的从绑定好的localPool中,把自己又存进去,因为handle已经绑定了对象。
  • 需要注意的是,localPool本身的线程安全问题。因为存对象时,没有了ThreadLocal,就会发生多个线程同时调用存操作,这时候,就需要localPool本身需要是线程安全的。

        @Override
        public void recycle(Object object) {
            if (object != value) {
                throw new IllegalArgumentException("object does not belong to handle");
            }
            //把自己存起来
            localPool.release(this);
        }
        //set 就是将对象放在成员变量中。
         void set(T value) {
            this.value = value;
        }
  • 这里还需要引出一个小问题,就是handle如果多次操作recycle().总不能将同一个handle不断的存入localPool吧,比如:get()时创建了一个handle,handle再多次操作recycle()方法后,又多次调用get(),就有可能多次得到同样的handle,那么之后的操作就不可控了。所以,Handle中有一个state来标识是否存入了localPool。
 private static final class DefaultHandle<T> implements Handle<T> {
        private static final int STATE_CLAIMED = 0;
        private static final int STATE_AVAILABLE = 1;
        private static final AtomicIntegerFieldUpdater<DefaultHandle<?>> STATE_UPDATER;
        static {
            AtomicIntegerFieldUpdater<?> updater = AtomicIntegerFieldUpdater.newUpdater(DefaultHandle.class, "state");
            //noinspection unchecked
            STATE_UPDATER = (AtomicIntegerFieldUpdater<DefaultHandle<?>>) updater;
        }

        @SuppressWarnings({"FieldMayBeFinal", "unused"}) // Updated by STATE_UPDATER.
        private volatile int state; // State is initialised to STATE_CLAIMED (aka. 0) so they can be released.
        //将存入的数据,state转为claim
         boolean availableToClaim() {
            if (state != STATE_AVAILABLE) {
                return false;
            }
            return STATE_UPDATER.compareAndSet(this, STATE_AVAILABLE, STATE_CLAIMED);
        }
       //将state标识为available
        void toAvailable() {
            int prev = STATE_UPDATER.getAndSet(this, STATE_AVAILABLE);
            if (prev == STATE_AVAILABLE) {
                throw new IllegalStateException("Object has been recycled already.");
            }
        }
       

而在localPool也有体现,state转变的调用


    private static final class LocalPool<T> {
        private final int ratioInterval;
        private volatile MessagePassingQueue<DefaultHandle<T>> pooledHandles;
        private int ratioCounter;

        @SuppressWarnings("unchecked")
        LocalPool(int maxCapacity, int ratioInterval, int chunkSize) {
            this.ratioInterval = ratioInterval;
            if (BLOCKING_POOL) {
                pooledHandles = new BlockingMessageQueue<DefaultHandle<T>>(maxCapacity);
            } else {
                pooledHandles = (MessagePassingQueue<DefaultHandle<T>>) newMpscQueue(chunkSize, maxCapacity);
            }
            ratioCounter = ratioInterval; // Start at interval so the first one will be recycled.
        }
        //取对象,
        DefaultHandle<T> claim() {
            MessagePassingQueue<DefaultHandle<T>> handles = pooledHandles;
            if (handles == null) {
                return null;
            }
            DefaultHandle<T> handle;
            do {
                handle = handles.relaxedPoll();
                // 循环的条件是 handle 不为null 且 handle转为claim()不成功或hanle为available
            } while (handle != null && !handle.availableToClaim());
            return handle;
        }
       // 将对象存入localpool
        void release(DefaultHandle<T> handle) {
            MessagePassingQueue<DefaultHandle<T>> handles = pooledHandles;
            //设置handle的state为available
            handle.toAvailable();
            if (handles != null) {
                handles.relaxedOffer(handle);
            }
        }
        //调节存数据的频率
        DefaultHandle<T> newHandle() {
            if (++ratioCounter >= ratioInterval) {
                ratioCounter = 0;
                return new DefaultHandle<T>(this);
            }
            //只有DefaultHandle 才能存对象到localPool中
            return null;
        }
    }
  • 之前强调过的localPool本身的线程安全问题就是看你使用哪种MessagePassingQueue,又两种选择,一种是Jctools的队列,一种是ArrayDeque,ArrayDeque在poll() offer() 都用synchronized 来保证线程安全。
//MessagePassingQueue 如果是ArrayDeque
 @Override
        public synchronized boolean offer(T e) {
            if (deque.size() == maxCapacity) {
                return false;
            }
            return deque.offer(e);
        }
 @Override
        public synchronized T poll() {
            return deque.poll();
        }

        @Override
        public synchronized T peek() {
            return deque.peek();
        }

最后就是
就是ThreadLocal要清除自己的值,就需要将LocalPool清除掉。

 private final FastThreadLocal<LocalPool<T>> threadLocal = new FastThreadLocal<LocalPool<T>>() {
        @Override
        protected LocalPool<T> initialValue() {
            return new LocalPool<T>(maxCapacityPerThread, interval, chunkSize);
        }
        //清除
        @Override
        protected void onRemoval(LocalPool<T> value) throws Exception {
            super.onRemoval(value);
            MessagePassingQueue<DefaultHandle<T>> handles = value.pooledHandles;
            value.pooledHandles = null;
            handles.clear();
        }
    };

总结下,recycler 可以达到对象复用的效果,但是并不严格保证每个对象都需要复用,localPool也有自己的最大容量,且ratioInterval 也可调节(这部分代码在Local Pool newHandle() 中体现)。但是在复用的过程中,看到了线程安全的考量。较之前的版本,更容易理解、没那么复杂了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值