美文网首页
ThreadLocal解析与最佳实践

ThreadLocal解析与最佳实践

作者: LNAmp | 来源:发表于2016-08-04 22:52 被阅读1059次

    概述

    用于同一个线程内的方法要共享某些变量或状态的时候,提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度

    源码解读

    源码的阅读主要集中在几个关键方法

    构造函数

      /**
         * Creates a thread local variable.
         */
        public ThreadLocal() {
        }
    

    可以看出,默认的构造函数什么都没有干,但如果需要设置初始值怎么办

    initialValue()

        protected T initialValue() {
            return null;
        }
    

    使用者可以通过继承ThreadLocal覆盖该方法来设置初始值,该值在第一次调用get()方法时被调用,该方法在整个ThreadLocal的生命周期中应该只对调用一次,除非用户显示地调用了remove(),然后又调用get()时会再次调用initialValue

    get()

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }
    

    get方法中可以看出先得到当前线程的threadlocalmap,如果不存在该map(首次调用get()),则调用setInitialValue(),如果存在则得到当前Key对应的值

    setInitialValue()

    private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
    

    先调用intialValue得到初始值,然后得到该线程对应的ThreadLocalMap,然后在Map中set初始值,如果没有ThreadLocalMap则创建,并设置当前TheadLocal初始值.从上可以看出,初始化的时候可能做两件事
    1、已有map
    则将ThreadLocal作为key,initialValue为value放入到map中

    2、没有map
    新建一个ThreadLocalMap,并将<key,value>放入其中

    createMap()

        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    set(T value)

       public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    和setInitialValue方法类似

    remove()

         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    

    比较重要的一个方法,将当前的threadlocal变量从map中移除。

    tips

    比较重要的一点是,ThreadLocal,Thread,ThreadLocalMap的设计
    目前的设计是Thread中有ThreadLocalMap,Map中以ThreadLocal为key,这种设计非常的清晰,由于在ThreadLocalMap中ThreadLocal是以WeakReference的形式存在的,所以其引用链或如下所示,也会产生GC疑问:ThreadLocal被回收,但是map中的entry一直不能回收的问题。
    所以引出了最佳实践问题

    threadlocal的引用链

    ThreadLocal引用链.png

    最佳实践

    最佳实践的方法参见google guava eventbus中对于ThreadLocal的使用

    
        private final ThreadLocal<Boolean> dispatching;
        
        this.dispatching = new ThreadLocal() {
                    protected Boolean initialValue() {
                        return Boolean.valueOf(false);
                    }
                }
        if(!((Boolean)this.dispatching.get()).booleanValue()) {
                    this.dispatching.set(Boolean.valueOf(true));
    
                    Dispatcher.PerThreadQueuedDispatcher.Event nextEvent;
                    try {
                        while((nextEvent = (Dispatcher.PerThreadQueuedDispatcher.Event)queueForThread.poll()) != null) {
                            while(nextEvent.subscribers.hasNext()) {
                                ((Subscriber)nextEvent.subscribers.next()).dispatchEvent(nextEvent.event);
                            }
                        }
                    } finally {
                        this.dispatching.remove();
                        this.queue.remove();
                    }
                }
    
    
    • 采用匿名内部类赋初始值
    • 显式调用get()、set()
    • 在不用的时候显式地remove()掉
    • 对于显示的remove特别重要,因为这样可以避免entry不被GC的情况
      如果为了避免ThreadLocal被GC,可以加强ThreadLocal的引用,将其声明成private static

    致谢

    本文参考了解密ThreadLocal,引用了文中的图片,感谢作者,侵删。

    相关文章

      网友评论

          本文标题:ThreadLocal解析与最佳实践

          本文链接:https://www.haomeiwen.com/subject/bbhhsttx.html