美文网首页
ThreadLocal全面解析,带你一起看源码

ThreadLocal全面解析,带你一起看源码

作者: 怪咖大蜀 | 来源:发表于2018-07-27 15:01 被阅读0次

    一、ThreadLocal作用

    该类提供线程本地变量。这些变量不同于它们的正常对应方,因为每个访问(通过其 get或set方法)都有自己独立初始化的变量副本。通俗一点就是存放在ThreadLocal的变量值作用于当前线程。每个线程获取到的不是同一个。

    二、使用方法

    public class Test {
        private  static ThreadLocal<String> threadLocal = new ThreadLocal<>();
        private void init() {
            threadLocal.set("text");
            threadLocal.get();
        }
        public static void main(String[] args){
            threadLocal.set("MainThreadData");
            String mainThreadLocalString = threadLocal.get();
            System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+mainThreadLocalString);
            secondThread.start();
            thirdThread.start();
            fourthThread.start();
        }
    
        public static  Thread secondThread = new Thread("SecondThread"){
            @Override
            public void run() {
                super.run();
                threadLocal.set("SecondThreadData");
                String secondThreadLocalData = threadLocal.get();
                System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+secondThreadLocalData);
            }
        };
        public static  Thread thirdThread = new Thread("ThirdThread"){
            @Override
            public void run() {
                super.run();
                String thirdThreadLocalData = threadLocal.get();
                System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+thirdThreadLocalData);
            }
        };
        public static  Thread fourthThread = new Thread("FourthThread"){
            @Override
            public void run() {
                super.run();
                threadLocal.set("FourthThreadData");
                String fourthThreadLocalData = threadLocal.get();
                System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+fourthThreadLocalData);
                threadLocal.remove();
                fourthThreadLocalData = threadLocal.get();
                System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+fourthThreadLocalData);
            }
        };
    }
    
    //结果
    ThreadName:main---Data:MainThreadData
    ThreadName:ThirdThread---Data:null
    ThreadName:FourthThread---Data:FourthThreadData
    ThreadName:FourthThread---Data:null
    ThreadName:SecondThread---Data:SecondThreadData
    

    我们可以看到 我们在不同线程中设置set不同的值,用get方法得到的也只是当前线程set的那个值,与其他线程无关。如果当前线程没有set,直接调用get方法,默认值为null。通过remove方法我们可以去清除我们set的值。

    三、源码分析

    我们看到ThreadLocal主要有三个方法,set、get、remove、下面我们来分析一下每个方法,我们先来看一下set方法

        /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Most subclasses will have no need to
         * override this method, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current thread's copy of
         *        this thread-local.
         */
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    我们可以看到set方法首先获取当前的Thread,并根据获取到的Thread调用getMap方法获取一个ThreadLocalMap,我们看一下getMap方法是怎么拿到ThreadLocalMap的

       /**
         * Get the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param  t the current thread
         * @return the map
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    

    getMap只有一行代码很明显可以看出是调用当前线程Thread的成员变量threadLocals,我们到Thread类中可以看到这个成员变量,

        ThreadLocal.ThreadLocalMap threadLocals = null;
    

    我们先不管ThreadLocalMap是什么,我们先看一下他是怎么进行创建的。我们还是回到ThreadLocal的set方法中,我们可以看到 当threadLocalMap为空的时候会调用 createMap(t, value);方法把当前现成threadLocal和我们set的Value传递过去。

        /**
         * Create the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the map
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    我们发先原来创建原来这么简单 只是new了一个ThreadLocalMap对象构造方法中传递this和我们set的Value,并把这个对象赋值给Thread的threadLocals。现在我们知道了原来我们的Thread中都会存放一个ThreadLocalMap。不同的Thread里面的ThreadLocalMap是不同的。我们看一下ThreadLocalMap方法并看一下ThreadLocalMap是干什么的,首先我们知道ThreadLocalMap是ThreadLocal的内部类。看一下构造方法。

         /**
             * Construct a new map initially containing (firstKey, firstValue).
             * ThreadLocalMaps are constructed lazily, so we only create
             * one when we have at least one entry to put in it.
             */
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
    

    我们可以看到ThreadLocalMap中有一个Entry类型的数组。并根据当前ThreadLocal的hashCode&(INITIAL_CAPACITY-1)得到一个值作为数组角标,然后new一个Entry对象将我们的ThreadLocal和Value存放到Entry中再存放到数组中对应的位置上。是不是有点和HashMap 有些相似。到现在为止我们基本上已经了解当我们第一次set的时候,我们的ThreadLocal是怎么把我们的数据存放到单独线程中的。我们最后看一下Entry是一个什么,Entry是ThreadLocalMap中的内部类,继承自若引用WeakReference,并将我们的set的Value存放到Entry中,如果大家不了解若引用的相关知识,大家可以自行百度学习。这里就不再赘述了。

          static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

    接下来我们看一下第二次set,我们的ThreadLocal是怎么设置的,我们通过ThreadLocal中的set方法可以看到当我们第二次进来的时候 map不为空会调用ThreadLocalMap的set方法

            /**
             * Set the value associated with key.
             *
             * @param key the thread local object
             * @param value the value to be set
             */
            private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    

    我们可以明显的看出我们还是根据我们ThreadLocal的哈希值去找到我们在Entry数组中应该存放的位置,如果这个位置已经有Entry了我们拿这个Entry的get()也就是我们创建Entry时候的那个ThreadLoal和我们现在的ThreadLocal相比是不是同一个,如果是同一个,直接将我们得到的Entry的Value设置成我们set的Value,如果不是我们会向下寻找,因为我们的Entry是软引用,当我们垃圾回收期工作的时候有可能会被回收,我们就会调用replaceStaleEntry方法将我们的table数组进行清理,并在我们的Entry数组中找到一个空槽或者是和我们相同ThreadLoal的Entry的位置,将我们数据存放到数组里面。如果我们通过我们ThreadLocal哈希值算出的位置上没有Entry的话。就直接新new一个Entry放到对应位置。到此我们的set方法已经全部分析完了。有没有些小激动。接下来我们接着分析get方法

        /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        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();
        }
    

    get方法比较简单,和set一样我们只需要获取到当前线程的ThreadLocalMap,根据我们的ThreadLocal获取到我们存放Value的Entry拿到我们的Value就可以了。如果我们在get之前没有调用set,我们这里就找不到我们要的Entry,就会调用我们的setInitialValue方法

        /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }
    

    这个方法的主要目的是设置一个默认值。在这个方法中首先会调用initialValue,如果你没复写这个方法,默认返回的是null,并判断是否存在ThreadLocalMap如果有进行set方法。如果没有进行createMap方法创建Map存入我们的值。并返回我们的默认Value,这两个方法我们在文章的前面已经进行分析过了。相信小伙伴们已经明白了。如果我们想给我们的ThreadLocal设置一个默认值的话,就可以重写initialValue();这个方法哦。

    最后。和大家说一下Entry继承弱引用的原因,Entry属于ThreadLocalMap,ThreadLocalMap属于Thread,当我们创建ThreadLocal的时候是强引用,我们用这个ThreadLocal存取数据的时候,Entry[]当中的我们当前ThreadLocal对应的Entry<ThreadLocal>这个弱引用是不会被垃圾回收的。因为我们的ThreadLocal还在被强引用连接着。但是当我们把我们的ThreadLocal置空的话,垃圾回收器进行回收的时候,我们Entry数组当中的Entry<ThreadLocal>就会一起被回收,因为没有强引用指向了,这里优化了内存的使用。提高了运行效率。你get到了么?

    相关文章

      网友评论

          本文标题:ThreadLocal全面解析,带你一起看源码

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