Java中的ThreadLocal和 InheritableTh

作者: 一字马胡 | 来源:发表于2017-10-10 00:18 被阅读771次

    作者: 一字马胡
    转载标志 【2017-11-03】

    更新日志

    日期 更新内容 备注
    2017-11-03 添加转载标志 持续更新

    ThreadLocal

    ThreadLocal从字面理解就是线程本地变量,貌似是一种线程私有的缓存变量的容器。为了说明ThreadLocal的特点,举个例子:比如有三个人,每个人比作一个线程,它们都需要一个袋子来装捡到的东西,也就是每个线程都希望自己有一个容器,当然,自己的捡到的东西肯定不希望和别人分享啊,也就是希望这个容器对其他人(线程)是不可见的,如果现在只有一个袋子,那怎么办?

    1. 每个人在捡东西之前一定会先抢到那个唯一的袋子,然后再捡东西,如果使用袋子的时间到了,就会马上把里面的东西消费掉,然后把袋子放到原来的地方,然后再次去抢袋子。这个方案是使用锁来避免线程竞争问题的,三个线程需要竞争同一个共享变量。
    2. 我们假设现在不是只有一个袋子了,而是有三个袋子,那么就可以给每个人安排一个袋子,然后每个人的袋子里面的对象是对其他人不可见的,这样的好处是解决了多个人竞争同一个袋子的问题。这个方案就是使用ThreadLocal来避免不必要的线程竞争的。

    大概了解了ThreadLocal,下面来看看它的使用方法:

    
        private static class UnsafeThreadClass {
    
            private int i;
    
            UnsafeThreadClass(int i) {
                this.i = i;
            }
    
            int getAndIncrement() {
                return ++ i;
            }
    
            @Override
            public String toString() {
                return "[" + Thread.currentThread().getName() + "]" + i;
            }
        }
        
     private static ThreadLocal<UnsafeThreadClass> threadLocal = new ThreadLocal<>();
    
        static class ThreadLocalRunner extends Thread {
            @Override
            public void run() {
    
                UnsafeThreadClass unsafeThreadClass = threadLocal.get();
    
                if (unsafeThreadClass == null) {
                    unsafeThreadClass = new UnsafeThreadClass(0);
                    threadLocal.set(unsafeThreadClass);
                }
    
                unsafeThreadClass.getAndIncrement();
    
                System.out.println(unsafeThreadClass);
            }
    
        }
    
    

    上面的例子仅仅是为了说明ThreadLocal可以为每个线程保存一个本地变量,这个变量不会受到其他线程的干扰,你可以使用多个ThreadLocal来让线程保存多个变量,下面我们分析一下ThreadLocal的具体实现细节,首先展示了ThreadLocal提供的一些方法,我们重点关注的是get、set、remove方法。

    ThreadLocal方法

    首先,我们需要new一个ThreadLocal对象,那么ThreadLocal的构造函数做了什么呢?

    
        /**
         * Creates a thread local variable.
         * @see #withInitial(java.util.function.Supplier)
         */
        public ThreadLocal() {
        }
    
    

    很遗憾它什么都没做,那么初始化的过程势必是在首次set的时候做的,我们来看一下set方法的细节:

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

    看起来首先根据当前线程获取到了一个ThreadLocalMap,getMap方法是做了什么?

    
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
    

    非常的简洁,是和Thread与生俱来的,我们看一下Thread中的相关定义:

    
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    

    关于inheritableThreadLocals将在下一小节再学习总结。

    获得了线程的ThreadLocalMap之后,如果不为null,说明不是首次set,直接set就可以了,注意key是this,也就是当前的ThreadLocal啊不是Thread。如果为空呢?说明还没有初始化,那么就需要执行createMap这个方法:

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

    没什么特别的,就是初始化线程的threadLocals,然后设定key-value。

    下面分析一下get的逻辑:

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

    和set一样,首先根据当前线程获取ThreadLocalMap,然后判断是否为null,如果为null,说明ThreadLocalMap还没有被初始化啊,那么就返回方法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;
        }
        
        protected T initialValue() {
            return null;
        }
    

    最后会返回null,但是会做一些初始化的工作,和set一样。在get里面,如果返回的ThreadLocalMap不为null,则说明ThreadLocalMap已经被初始化了,那么就可以正常根据ThreadLocal作为key获取了。

    当线程退出时,会清理ThreadLocal,可以看下面的代码:

    
        /**
         * This method is called by the system to give a Thread
         * a chance to clean up before it actually exits.
         */
        private void exit() {
            if (group != null) {
                group.threadTerminated(this);
                group = null;
            }
            /* Aggressively null out all reference fields: see bug 4006245 */
            target = null;
            /* Speed the release of some of these resources */
            threadLocals = null;
            inheritableThreadLocals = null;
            inheritedAccessControlContext = null;
            blocker = null;
            uncaughtExceptionHandler = null;
        }
    
    

    这里做了大量“Help GC”的工作。包括我们本节所讲的threadLocals和下一小节要讲的inheritableThreadLocals都会被清理。

    如果我们想要显示的清理ThreadLocal,可以使用remove方法:

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

    逻辑较为直接,很好理解。

    InheritableThreadLocal

    ThreadLocal固然很好,但是子线程并不能取到父线程的ThreadLocal的变量,比如下面的代码:

    
        private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
        private static InheritableThreadLocal<Integer> inheritableThreadLocal =
                new InheritableThreadLocal<>();
    
        public static void main(String[] args) throws InterruptedException {
    
            integerThreadLocal.set(1001); // father
            inheritableThreadLocal.set(1002); // father
    
            new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
                    + integerThreadLocal.get() + "/"
                    + inheritableThreadLocal.get())).start();
    
        }
    
    //output:
    Thread-0:null/1002
    
    

    使用ThreadLocal不能继承父线程的ThreadLocal的内容,而使用InheritableThreadLocal时可以做到的,这就可以很好的在父子线程之间传递数据了。下面我们分析一下InheritableThreadLocal的实现细节,下面展示了InheritableThreadLocal提供的方法:

    InheritableThreadLocal方法

    InheritableThreadLocal继承了ThreadLocal,然后重写了上面三个方法,所以除了上面三个方法之外,其他所有对InheritableThreadLocal的调用都是对ThreadLocal的调用,没有什么特别的。我们上文中提到了Thread类,里面有我们本文关心的两个成员,我们来看一下再Thread中做了哪些工作,我们跟踪一下new一个Thread的调用路径:

    
    new Thread()
    
    init(ThreadGroup g, Runnable target, String name, long stackSize)
                           
    
    init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals)
    
    -> 
               if (inheritThreadLocals && parent.inheritableThreadLocals != null)
                this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
    createInheritedMap(ThreadLocalMap parentMap)
    
    
    ThreadLocalMap(ThreadLocalMap parentMap) 
    
    

    上面列出了最为关键的代码,可以看到,最后会调用ThreadLocal的createInheritedMap方法,而该方法会新建一个ThreadLocalMap,看一下构造函数的内容:

    
            private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    
                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        @SuppressWarnings("unchecked")
                        ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                        if (key != null) {
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            int h = key.threadLocalHashCode & (len - 1);
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }
    
    

    parentMap就是父线程的ThreadLocalMap,这个构造函数的意思大概就是将父线程的ThreadLocalMap复制到自己的ThreadLocalMap里面来,这样我们就可以使用InheritableThreadLocal访问到父线程中的变量了。

    对ThreadLocal更为具体和深入的分析将在其他的篇章中进行,本文点到即可,为了深入理解ThreadLocal,可以阅读ThreadLocalMap的源码,以及可以在项目中多思考是否可以使用ThreadLocal来做一些事情,比如,如果我们具有这样一种线程模型,一个任务从始至终只会被一个线程执行,那么可以使用ThreadLocal来计算运行该任务的时间。

    相关文章

      网友评论

      本文标题:Java中的ThreadLocal和 InheritableTh

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