Netty4 FastThreadLocal

FastThreadLocal

        FastThreadLocal是Netty对jdk中ThreadLocal进行封装和优化,解决jdk中自带的ThreadLocal 在线程池使用环境中,有内存泄漏的风险问题。它和ThreadLocal有着一样的功能,从名称来看Fast是快的意思,那么它为什么快?下面我们来讨论一下

如何使用

    @Test
    public void test() {
        // 1.创建FastThreadLocal
    	FastThreadLocal<String> fastThreadLocal = new FastThreadLocal<String>();
        // 2.赋值
    	fastThreadLocal.set("hello fastThreadLocal");    
        // 3.获取值
    	System.out.println(fastThreadLocal.get());
        // 4.删除
    	fastThreadLocal.remove();
    	System.out.println(fastThreadLocal.get());
    }

测试结果: 

hello fastThreadLocal
null

1. 创建FastThreadLocal

每创建一个FastThreadLocal都会自动生成index索引,这个index作用很大,后面会说到,先留着

    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }


    //InternalThreadLocalMap.nextVariableIndex()
    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }    

2. 赋值  set()

    public final void set(V value) {
        if (value != InternalThreadLocalMap.UNSET) {
            InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
            if (setKnownNotUnset(threadLocalMap, value)) {
                registerCleaner(threadLocalMap);
            }
        } else {
            remove();
        }
    }

2.1  InternalThreadLocalMap.get()   从当前线程中获取InternalThreadLocalMap

    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

    private static InternalThreadLocalMap slowGet() {
        ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

先判断当前线程是不是FastThreadLocalThread,若是通过fastGet直接获取,这个方法很简单,先获取,没有就创建,若不是,则通过slowGet方法获取,这个方法主要是用JDK的ThreadLocal来保存InternalThreadLocalMap,逻辑和fasetGet相似,这里就不在赘述了。

2.2  setKnownNotUnset() 

   private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
        if (threadLocalMap.setIndexedVariable(index, value)) {
            addToVariablesToRemove(threadLocalMap, this);
            return true;
        }
        return false;
    }

将value数据插入到InternalThreadLocalMap中 indexedVariables,这个index就是实例化FastThreadLocal生成的(唯一的),当数组满时,就需要扩容了

    public boolean setIndexedVariable(int index, Object value) {
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            Object oldValue = lookup[index];
            lookup[index] = value;
            return oldValue == UNSET;
        } else {
            // 扩容
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }

    // 每次扩容是以前的2倍
    private void expandIndexedVariableTableAndSet(int index, Object value) {
        Object[] oldArray = indexedVariables;
        final int oldCapacity = oldArray.length;
        int newCapacity = index;
        newCapacity |= newCapacity >>>  1;
        newCapacity |= newCapacity >>>  2;
        newCapacity |= newCapacity >>>  4;
        newCapacity |= newCapacity >>>  8;
        newCapacity |= newCapacity >>> 16;
        newCapacity ++;

        Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
        Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
        newArray[index] = value;
        indexedVariables = newArray;
    }

当数据插入进去,就执行addToVariablesToRemove(), 该方法主要是将自己本身(FastThreadLocal)放入到InternalThreadLocalMap中,当然首先先判断有没有放入进去。

    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        Set<FastThreadLocal<?>> variablesToRemove;
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }

        variablesToRemove.add(variable);
    }

这里为什么要FastThreadLocal放入到InternalThreadLocalMap,原因是避免了内存泄漏的风险问题,后面会谈到。

 

2.3  registerCleaner()

注册清除器, 避免了内存泄漏的风险问题

    private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
        Thread current = Thread.currentThread();
        if (!FastThreadLocalThread.willCleanupFastThreadLocals(current)) {
            // We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released
            // and FastThreadLocal.onRemoval(...) will be called.
            ObjectCleaner.register(current, new Runnable() {
                @Override
                public void run() {
                    remove(threadLocalMap);

                    // It's fine to not call InternalThreadLocalMap.remove() here as this will only be triggered once
                    // the Thread is collected by GC. In this case the ThreadLocal will be gone away already.
                }
            });
        }
    }

2.3.1  先判断 thread instanceof FastThreadLocalThread && ((FastThreadLocalThread) thread).willCleanupFastThreadLocals();当前线程是否需要注册内存清除器,若需要进入ObjectCleaner.register()方法

    public static void register(Object object, Runnable cleanupTask) {
        AutomaticCleanerReference reference = new AutomaticCleanerReference(object,
                ObjectUtil.checkNotNull(cleanupTask, "cleanupTask"));
        LIVE_SET.add(reference);
        if (CLEANER_RUNNING.compareAndSet(false, true)) {
            Thread cleanupThread = new FastThreadLocalThread(CLEANER_TASK);
            cleanupThread.setPriority(Thread.MIN_PRIORITY);
            cleanupThread.setContextClassLoader(null);
            cleanupThread.setName(CLEANER_THREAD_NAME);
            cleanupThread.setDaemon(false);
            cleanupThread.start();
        }
    }

2.3.1.1. 先创建AutomaticCleanerReference对象,这个AutomaticCleanerReference是继承WeakReference对象的

2.3.1.2. 将AutomaticCleanerReference对象 添加到LIVE_SET中

2.3.1.3. 这个清理线程只执行一次,CLEANER_TASK 是无限循环Runnable,这个死循环会不断的判断LIVE_SET中数据是否没有被引用,若没有,直接销毁FastThreadLocal数据,避免了内存泄漏的风险问题。最终会调用FastThreadLocal.remove()

    private static final Runnable CLEANER_TASK = new Runnable() {
        @Override
        public void run() {
            boolean interrupted = false;
            for (;;) {
                while (!LIVE_SET.isEmpty()) {
                    final AutomaticCleanerReference reference;
                    try {
                        reference = (AutomaticCleanerReference) REFERENCE_QUEUE.remove(REFERENCE_QUEUE_POLL_TIMEOUT_MS);
                    } catch (InterruptedException ex) {
                        interrupted = true;
                        continue;
                    }
                    if (reference != null) {
                        try {
                            reference.cleanup();
                        } catch (Throwable ignored) {
                        }
                        LIVE_SET.remove(reference);
                    }
                }
                CLEANER_RUNNING.set(false);
                if (LIVE_SET.isEmpty() || !CLEANER_RUNNING.compareAndSet(false, true)) {
                    break;
                }
            }
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }
    };

2.3.2 当不需要清理线程处理FastThreadLocal remove,就表示当前线程是FastThreadLocalThread 线程,那么它是怎么避免内存溢出问题呢?先看看FastThreadLocalThread的构造方法

    public FastThreadLocalThread(Runnable target) {
        super(FastThreadLocalRunnable.wrap(target));
        cleanupFastThreadLocals = true;
    }

    final class FastThreadLocalRunnable implements Runnable {
        private final Runnable runnable;

        private FastThreadLocalRunnable(Runnable runnable) {
            this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
        }

        @Override
        public void run() {
            try {
                runnable.run();
            } finally {
                FastThreadLocal.removeAll();
            }
        }

        static Runnable wrap(Runnable runnable) {
            return runnable instanceof FastThreadLocalRunnable ? runnable : new             FastThreadLocalRunnable(runnable);
        }
    }

注意到Runnable这个对象被封装成FastThreadLocalRunnable,当Runnable执行了run方法之后,回去执行FastThreadLocal,removeAll();

    public static void removeAll() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
        if (threadLocalMap == null) {
            return;
        }

        try {
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            if (v != null && v != InternalThreadLocalMap.UNSET) {
                @SuppressWarnings("unchecked")
                Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
                FastThreadLocal<?>[] variablesToRemoveArray =
                        variablesToRemove.toArray(new FastThreadLocal[variablesToRemove.size()]);
                for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                    tlv.remove(threadLocalMap);
                }
            }
        } finally {
            InternalThreadLocalMap.remove();
        }
    }

1.先获取当前线程的InternalThreadLocalMap

2.通过Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); 获取FastThreadLocal

3.执行FastThreadLocal.remove()

4.清除FastThreadLocal 在InternalThreadLocalMap的数组中

以上就解释为什么要记录自身(FastThreadLocal)放在InternalThreadLocalMap的数组中,就是因为在当线程执行完成后,清除FastThreadLocal,避免内存溢出。

3. get()

    public final V get() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        Object v = threadLocalMap.indexedVariable(index);
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }

        V value = initialize(threadLocalMap);
        registerCleaner(threadLocalMap);
        return value;
    }

1.先获取当前InternalThreadLocalMap

2.根据index获取相应的缓存值

3.若不是NULL,直接返回,若是NULL则通过initialize() 去获取,当时initialize方法是用户自定义的

4.注册线程清除器,上面已聊过

4. remove()

    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }

        Object v = threadLocalMap.removeIndexedVariable(index);
        removeFromVariablesToRemove(threadLocalMap, this);

        if (v != InternalThreadLocalMap.UNSET) {
            try {
                onRemoval((V) v);
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }

1. 获取缓存对象

2. 设置InternalThreadLocalMap的数组为NULL

3. 清除FastThreadLocal 在InternalThreadLocalMap的数组中

4. onRemoval() 是空方法,需要用户自定义实现

总结一下

FastTheadLocal与ThreadLocal比较:

优点: 1. FastTheadLocal性能也会比 JDK 的好,因为它是用数组,而JDK 的使用线性探测法的 Map

            2. FastTheadLocal避免内存泄漏问题

缺点:   每个FastThreadLocal的index都是唯一的,  当FastThreadLocal下标很大的时候,需要扩容,感觉有点浪费

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值