ThreadLocal原理与源码分析

来源:互联网 发布:我的淘宝怎么注册账号 编辑:程序博客网 时间:2024/05/15 10:31

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

  分析前感谢这篇博客:http://www.cnblogs.com/dolphin0520/p/3920407.html,没有这篇我真的弄不懂,这篇观点正确,诲人不倦,一定是位技术深厚的前辈!

  好,闲言少叙,讲正题。 
  先是一个使用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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  这是一个没什么用的例子,只为说明问题。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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  来一行行看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;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  看到这里可能很惊奇,“纳尼?键的类型是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;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  传进去的是当前线程,返回来的是当前线程对象里的一个成员变量!而且类型是ThreadLocalMap!“纳尼?!Thread类里有这个成员变量?”没错,Thread类里持有一个ThreadLocalMap对象!不信跳过去看,Thread类里有这句话:

/* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

  这是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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  获取到了当前线程的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这个名字,觉得像冷笑话一样,“线程本地”,意思是说“线程自己拿自己的本地空间(线程里的局部变量)存数据”。

0 0