一.ThreadLocal 源码分析

402 阅读4分钟
最近在回顾一下Handler的源码,自然而然延伸出多线程的一些问题,然后Looper就是主角了,细心的同学看源码肯定发现这个looper是存放在ThreadLocal中的,今天咱们就来分析一下它到底是一个什么东西。

image.png

  • ThreadLocal的作用.

隔离多个线程私有的数据,防止多线程并发情况下自己线程的数据被其他线程读写。

ThreadLocal 的使用非常的简单。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("测试ThreadLocal");
String result = threadLocal.get();
System.out.println("result = " + result);//result = 测试ThreadLocal
threadLocal.remove();
  • set和get和remove方法
/**
     * 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();//首先获取当前的线程,暂且理解为一个key
        ThreadLocalMap map = getMap(t);//通过当前线程,获取ThreadLocalMap
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    /**
     * 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();//不存在的时候返回初始值null
    }
    
    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
  • 上面的代码比较容易理解,上面三个方法都用到了getMap方法
/**
     * 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;//ThreadLocalMap为Thread的成员变量。每个Thread都有自己的ThreadLocalMap。到此同学们应该知道为什么ThreadLocal为什么可以实现线程间数据隔离了,就是因为数据都存储在ThreadLocalMap中。
    }
  • ThreadLocalMap 从名字看它就是一个map,和大家熟悉的HashMap有些类似,但它有自己特点,咱们慢慢来分析它。
//ThreadLocal静态内部类
static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        ...
}

结构大概如下: image.png

它其实和HashMap很像,但是它没有链表和红黑树接口,那么遇到hash冲突怎么解决呢?

 /**
         * 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) {//当前数据位置已存在,并且key相同直接替换value
                    e.value = value;
                    return;
                }

                if (k == null) {//当前数据位置为null,创建一个entry放进去     
                    replaceStaleEntry(key, value, i);
                    return;
                }
                //这个循环还有一个隐藏的条件,如果当前位置数据已经存在,但是key不相等,则执行下一次循环 “e = tab[i = nextIndex(i, len)]”,继续查找下一个位置存放。
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

//这个循环还有一个隐藏的条件,如果当前位置数据已经存在,但是key不相等,则执行下一次循环 “e = tab[i = nextIndex(i, len)]”,继续查找下一个位置存放。

image.png

  • 共享数据 inheritableThreadLocals
//使用方式
private void inheritableThreadLocalTest() {
        inheritableThreadLocal.set("mainThread value");//在mainThread存入值
        new Thread("Thread~1") {
            @Override
            public void run() {
                super.run();
                //Thread~1 的parent是mainThread
                String value = inheritableThreadLocal.get();
                System.out.println("value = " + value + " ,currentThreadName: " + Thread.currentThread().getName());//value = mainThread value ,currentThreadName: Thread~1
            }
        }.start();
    }
//源码
public
class Thread implements Runnable {
   
    /* 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.
     */
     
    // 大家注意到Thread类中,除了threadLocals,还有一个成员变量inheritableThreadLocals,根据名字可猜测是可继承的threadlocals
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
/**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
       ...省略部分代码
       
       //1.这里复制(继承)了parent的inheritableThreadLocals,好奇parent是什么的小伙伴,看一下源码init中,Thread parent = currentThread();它是创建该线程的线程。
       //2.如何给parent的inheritableThreadLocals赋值呢?它的权限是默认的,我们无法在我们的代码中引入,需要借助jdk封装好的InheritableThreadLocal,这个源码很简单就不带大家看了。
       
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
                
    }
  • ThreadLocal中的内存泄漏问题
static class ThreadLocalMap {
    //Entry是弱引用对象,ThreadLocal是被弱应用对象,它会被gc回收,同学们别搞混了
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        ...
}

ThreadMap只有key是弱引用,value依然是强引用。

引用链如下:
Thread----ThreadLocalMap----Entry----value

当threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收,但是value依然不能回收(有一条强引用链),直到Thread销毁,value才能被回收。但是线程池中,线程不会销毁的情况下这种问题就无法解决了。

解决方案:调用remove方法。

/**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
        
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;//将value主动置为null,以便于gc回收
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }
  • 总结 ThreadLocal相对来说并不复杂,大家多看几遍源码大概能理清楚它的原理。第一次写文章,各位同学如果在阅读中发现问题,欢迎大家评论指正。 码字不易,喜欢的点个赞吧。谢谢大家!