查看原文
其他

ThreadLocal原理与源码分析

2017-04-19 IT哈哈

ThreadLocal,神神秘秘的一个东西,长久以来似乎都觉得“这玩意好屌!竟然能这么轻松地解决线程间资源冲突问题!”。然而分析下它的源码就会发现,这东西只是唬人的,原理其实就是“在各线程的堆空间里维护各线程自己的资源”,更通俗的说法就是“废话!你让每个线程在自己线程里面用自己的局部变量,发生冲突才怪!”。所以说啊这个东西就是个纸老虎,下面从头分析。

  分析前感谢这篇博客:Java并发编程:深入剖析ThreadLocal ,没有这篇我真的弄不懂,这篇观点正确,诲人不倦,一定是位技术深厚的前辈!

  好,闲言少叙,讲正题。 

  先是一个使用ThreadLocal的例子:

private static final ThreadLocal<Integer> CONTEXT = new ThreadLocal<>();

public void setVal(int i) {

        CONTEXT.set(i);

}

public int getVal(){

    Integer val = CONTEXT.get();

    return val == null ? 0 : val;

}

这是一个没什么用的例子,只为说明问题。setVal方法可以往现在这个线程的“线程本地空间”(先不用管这个名词啥意思,就当是线程间互不相关的各自的一块空间)里存进去一个Integer或者更新已有Integer的值;getVal方法是获取这个值。threadlocal的作用就在于可以让每个线程各有各的值,互不影响,线程安全! 

  那么从源码角度分析threadlocal是怎么做到的。 

  首先是看一下ThreadLocal.set(T value)这个方法:


/**

     * 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);

    }


来一行行看ThreadLocal在这里干了什么,先是获取当前线程,然后调用了getMap,传入当前线程作为参数,获得到了一个类型为ThreadLocalMap的对象。好,现在解决两个问题(注意接下来提到的类名): 

  1、ThreadLocalMap是啥? 

  跳过去看一下发现,ThreadLocalMap是ThreadLocal的一个内部类,ThreadLocalMap内部有一段这样的代码:


static class Entry extends WeakReference<ThreadLocal> {

    /** The value associated with this ThreadLocal. */

    Object value;


    Entry(ThreadLocal k, Object v) {

        super(k);

        value = v;

    }

}


看到这里可能很惊奇,“纳尼?键的类型是ThreadLocal?”,暂且搁置这个疑问,Entry的值是Object,嗯,看来每个线程里的“数据副本”就是存在这。 

  2、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对象!不信跳过去看,Thread类里有这句话:


/* ThreadLocal values pertaining to this thread. This map is maintained

     * by the ThreadLocal class. */

ThreadLocal.ThreadLocalMap threadLocals = null;


这是Thread类的一个成员变量。返回去的就是这个,每个Thread都自己有一个的,存在于该Thread栈空间的,和该Thread中声明的局部变量没什么区别的(从存储的角度上讲),一个Entry为<ThreadLocal, Object>的ThreadLocalMap。 

  回头看之前的ThreadLocal.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);

    }


获取到了当前线程的ThreadLocalMap之后,我们的ThreadLocal判断了一下map是否为空,不空就存入或替换为(this, value);空就执行一个初始化的操作。set或者初始化的细节本文不赘述了。 

  值得注意的是,之前我们很疑惑的“键为什么是ThreadLocal”这个谜题解开了,这里不是就传入了this这个ThreadLocal嘛,这是因为一个Thread可能对应不止一个ThreadLocal,想要知道具体是Thread对应的哪个ThreadLocal,就要在Thread中维护一个ThreadLocalMap,以ThreadLocal为键,就可以找到Thread在某个ThreadLocal里对应的本地数据(本地数据指的就是Entry值的那个Object,例子里的Integer,实际上以thread内部变量的形式存在于thread对象中),这就是“Thread里为啥有个ThreadLocalMap?ThreadLocalMap为啥是个Map?键的类型为啥是ThreadLocal?”这三个问题的答案! 

  上面一段话比较拗口,本人水平所限,只能说成这样了,真正理解上面的话也就理解了ThreadLocal。这时候你一定会拍着大腿说,我靠!这是个什么玩意儿?原来这么简单啊! 

  没错,ThreadLocal的根本原理在于把数据存在了线程的各自的ThreadLocalMap中,也就是存在了线程的一个成员变量里,线程自己的内部变量当然跟别的线程互不影响,当然解决了这个问题。也就是说上面的例子里,你完全可以自己给你的线程类里加一个Integer型的成员,再写个get、set方法,就能达到完全相同的效果,这不就是所谓的“给每个线程一份数据副本”吗?只不过JAVA为你提供了一个名为ThreadLocal的API让你可以方便的处理这件事,比如你需要在方法间跳来跳去的时候,或者数据类型没有Integter这么简单的话。ThreadLocal不过是个方便你管理线程里数据的一个JDK提供的API而已,没什么神奇的。 

  回头看ThreadLocal这个名字,觉得像冷笑话一样,“线程本地”,意思是说“线程自己拿自己的本地空间(线程里的局部变量)存数据”。


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存