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

2018-07-27  本文已影响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到了么?

上一篇下一篇

猜你喜欢

热点阅读