ThreadLocal

相信读者在网上也看了很多关于ThreadLocal的资料,很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路;ThreadLocal的目的是为了解决多线程访问资源时的共享问题。如果你也这样认为的,那现在给你10秒钟,清空之前对ThreadLocal的错误的认知!

看看JDK中的源码是怎么写的:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its 
{@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). 
翻译过来大概是这样的(英文不好,如有更好的翻译,请留言说明):

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。

可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。 
举个例子,我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数,我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡(北京公交和地铁都使用公交卡),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的

有人要说了:你可以将公交卡设置为全局变量啊,这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),这样不就乱套了嘛。现在明白了吧?这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。

ThreadLocal基本操作

构造函数

ThreadLocal的构造函数签名是这样的:

/** 
* Creates a thread local variable. 
* @see #withInitial(java.util.function.Supplier) 
*/ 
public ThreadLocal() { 

内部啥也没做。

initialValue函数

initialValue函数用来设置ThreadLocal的初始值,函数签名如下:

protected T initialValue() { 
return null; 

该函数在调用get函数的时候会第一次调用,但是如果一开始就调用了set函数,则该函数不会被调用。通常该函数只会被调用一次,除非手动调用了remove函数之后又调用get函数,这种情况下,get函数中还是会调用initialValue函数。该函数是protected类型的,很显然是建议在子类重载该函数的,所以通常该函数都会以匿名内部类的形式被重载,以指定初始值,比如:

<span style="color:#000000"><code><span style="color:#000088">package</span> com.winwill.test;
<span style="color:#880000">/**
 *<span style="color:#4f4f4f"> @author</span> qifuguang
 *<span style="color:#4f4f4f"> @date</span> 15/9/2 00:05
 */</span>
<span style="color:#000088">public</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">TestThreadLocal</span> {
    <span style="color:#000088">private</span> <span style="color:#000088">static</span> <span style="color:#000088">final</span> ThreadLocal<Integer> value = <span style="color:#000088">new</span> ThreadLocal<Integer>() {
        <span style="color:#9b859d">@Override</span>
        <span style="color:#000088">protected</span> Integer <span style="color:#009900">initialValue</span>() {
            <span style="color:#000088">return</span> Integer.valueOf(<span style="color:#006666">1</span>);
        }
    };
}</code></span>

get函数

该函数用来获取与当前线程关联的ThreadLocal的值,函数签名如下:

public T get() 
如果当前线程没有该ThreadLocal的值,则调用initialValue函数获取初始值返回。

set函数

set函数用来设置当前线程的该ThreadLocal的值,函数签名如下:

public void set(T value) 
设置当前线程的ThreadLocal的值为value。

remove函数

remove函数用来将当前线程的ThreadLocal绑定的值删除,函数签名如下:

public void remove() 
在某些情况下需要手动调用该函数,防止内存泄露。

代码演示

学习了最基本的操作之后,我们用一段代码来演示ThreadLocal的用法,该例子实现下面这个场景:

有5个线程,这5个线程都有一个值value,初始值为0,线程运行时用一个循环往value值相加数字。

代码实现:

<span style="color:#000000"><code><span style="color:#000088">package</span> com.winwill.test;
<span style="color:#880000">/**
 *<span style="color:#4f4f4f"> @author</span> qifuguang
 *<span style="color:#4f4f4f"> @date</span> 15/9/2 00:05
 */</span>
<span style="color:#000088">public</span> <span style="color:#000088">class</span> <span style="color:#4f4f4f">TestThreadLocal</span> {
    <span style="color:#000088">private</span> <span style="color:#000088">static</span> <span style="color:#000088">final</span> ThreadLocal<Integer> value = <span style="color:#000088">new</span> ThreadLocal<Integer>() {
        <span style="color:#9b859d">@Override</span>
        <span style="color:#000088">protected</span> Integer <span style="color:#009900">initialValue</span>() {
            <span style="color:#000088">return</span> <span style="color:#006666">0</span>;
        }
    };
    <span style="color:#000088">public</span> <span style="color:#000088">static</span> <span style="color:#000088">void</span> <span style="color:#009900">main</span>(String[] args) {
        <span style="color:#000088">for</span> (<span style="color:#000088">int</span> i = <span style="color:#006666">0</span>; i < <span style="color:#006666">5</span>; i++) {
            <span style="color:#000088">new</span> Thread(<span style="color:#000088">new</span> MyThread(i)).start();
        }
    }
    <span style="color:#000088">static</span> class MyThread implements Runnable {
        <span style="color:#000088">private</span> <span style="color:#000088">int</span> index;
        <span style="color:#000088">public</span> <span style="color:#009900">MyThread</span>(<span style="color:#000088">int</span> index) {
            <span style="color:#000088">this</span>.index = index;
        }
        <span style="color:#000088">public</span> <span style="color:#000088">void</span> <span style="color:#009900">run</span>() {
            System.out.println(<span style="color:#009900">"线程"</span> + index + <span style="color:#009900">"的初始value:"</span> + value.get());
            <span style="color:#000088">for</span> (<span style="color:#000088">int</span> i = <span style="color:#006666">0</span>; i < <span style="color:#006666">10</span>; i++) {
                value.set(value.get() + i);
            }
            System.out.println(<span style="color:#009900">"线程"</span> + index + <span style="color:#009900">"的累加value:"</span> + value.get());
        }
    }
}</code></span>
  •  

执行结果为:

线程0的初始value:0 
线程3的初始value:0 
线程2的初始value:0 
线程2的累加value:45 
线程1的初始value:0 
线程3的累加value:45 
线程0的累加value:45 
线程1的累加value:45 
线程4的初始value:0 
线程4的累加value:45

可以看到,各个线程的value值是相互独立的,本线程的累加操作不会影响到其他线程的值,真正达到了线程内部隔离的效果。

如何实现的

看了基本介绍,也看了最简单的效果演示之后,我们更应该好好研究下ThreadLocal内部的实现原理。如果给你设计,你会怎么设计?相信大部分人会有这样的想法:

每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。

没错,这是最简单的设计方案,JDK最早期的ThreadLocal就是这样设计的。JDK1.3(不确定是否是1.3)之后ThreadLocal的设计换了一种方式。

我们先看看JDK8的ThreadLocal的get方法的源码:

<span style="color:#000000"><code><span style="color:#000088">public</span> T get() {
      <span style="color:#000088">Thread</span> t <span style="color:#4f4f4f">=</span> <span style="color:#000088">Thread</span><span style="color:#4f4f4f">.</span>currentThread();
      ThreadLocalMap <span style="color:#4f4f4f">map</span> <span style="color:#4f4f4f">=</span> getMap(t);
      <span style="color:#000088">if</span> (<span style="color:#4f4f4f">map</span> <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
          ThreadLocalMap<span style="color:#4f4f4f">.</span>Entry e <span style="color:#4f4f4f">=</span> <span style="color:#4f4f4f">map</span><span style="color:#4f4f4f">.</span>getEntry(this);
          <span style="color:#000088">if</span> (e <span style="color:#4f4f4f">!=</span> <span style="color:#4f4f4f">null</span>) {
              @SuppressWarnings(<span style="color:#009900">"unchecked"</span>)
              T result <span style="color:#4f4f4f">=</span> (T)e<span style="color:#4f4f4f">.</span>value;
              <span style="color:#000088">return</span> result;
          }
      }
      <span style="color:#000088">return</span> setInitialValue();
  }</code></span>

其中getMap的源码:

<span style="color:#000000"><code>ThreadLocalMap getMap(Thread t) {
    <span style="color:#000088">return</span> t.threadLocals;
}
setInitialValue函数的源码:

<span style="color:#000088">private</span> T <span style="color:#009900">setInitialValue</span>() {
    T <span style="color:#000088">value</span> = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    <span style="color:#000088">if</span> (map != <span style="color:#000088">null</span>)
        map.<span style="color:#000088">set</span>(<span style="color:#000088">this</span>, <span style="color:#000088">value</span>);
    <span style="color:#000088">else</span>
        createMap(t, <span style="color:#000088">value</span>);
    <span style="color:#000088">return</span> <span style="color:#000088">value</span>;
}</code></span>

createMap函数的源码:

<span style="color:#000000"><code><span style="color:#000088">void</span> createMap(Thread t, T firstValue) {
    t.threadLocals = <span style="color:#000088">new</span> ThreadLocalMap(<span style="color:#000088">this</span>, firstValue);
}</code></span>

简单解析一下,get方法的流程是这样的: 
1. 首先获取当前线程 
2. 根据当前线程获取一个Map 
3. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的value e,否则转到5 
4. 如果e不为null,则返回e.value,否则转到5 
5. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map 
然后需要注意的是Thread类中包含一个成员变量:

ThreadLocal.ThreadLocalMap threadLocals = null; 
所以,可以总结一下ThreadLocal的设计思路: 
每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。 
这个方案刚好与我们开始说的简单的设计方案相反。查阅了一下资料,这样设计的主要有以下几点优势:

  1. 这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能,据说性能的提升不是一点两点(没有亲测)
  2. 当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。 
    再深入一点

先交代一个事实:ThreadLocalMap是使用ThreadLocal的弱引用作为Key的:

<span style="color:#000000"><code>static <span style="color:#000088">class</span> <span style="color:#4f4f4f">ThreadLocalMap</span> {
        <span style="color:#880000">/**
         * 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.
         */</span>
        static <span style="color:#000088">class</span> <span style="color:#4f4f4f">Entry</span> <span style="color:#000088">extends</span> <span style="color:#4f4f4f">WeakReference</span><<span style="color:#4f4f4f">ThreadLocal</span><?>> {
            <span style="color:#880000">/** The value associated with this ThreadLocal. */</span>
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                <span style="color:#000088">super</span>(k);
                value = v;
            }
        }
        ...
        ...
}</code></span>

下图是本文介绍到的一些对象之间的引用关系图,实线表示强引用,虚线表示弱引用: 
ThreadLocal

然后网上就传言,ThreadLocal会引发内存泄露,他们的理由是这样的: 
如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链: 
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 
永远无法回收,造成内存泄露。

我们来看看到底会不会出现这种情况。 
其实,在JDK的ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施,下面是ThreadLocalMap的getEntry方法的源码:

<span style="color:#000000"><code><span style="color:#000088">private</span> Entry <span style="color:#009900">getEntry</span>(ThreadLocal<?> key) {
    <span style="color:#000088">int</span> i = key.threadLocalHashCode & (table.length - <span style="color:#006666">1</span>);
    Entry e = table[i];
    <span style="color:#000088">if</span> (e != <span style="color:#000088">null</span> && e.<span style="color:#000088">get</span>() == key)
        <span style="color:#000088">return</span> e;
    <span style="color:#000088">else</span>
        <span style="color:#000088">return</span> getEntryAfterMiss(key, i, e);
}
getEntryAfterMiss函数的源码:

<span style="color:#000088">private</span> Entry <span style="color:#009900">getEntryAfterMiss</span>(ThreadLocal<?> key, <span style="color:#000088">int</span> i, Entry e) {
     Entry[] tab = table;
     <span style="color:#000088">int</span> len = tab.length;
     <span style="color:#000088">while</span> (e != <span style="color:#000088">null</span>) {
         ThreadLocal<?> k = e.<span style="color:#000088">get</span>();
         <span style="color:#000088">if</span> (k == key)
             <span style="color:#000088">return</span> e;
         <span style="color:#000088">if</span> (k == <span style="color:#000088">null</span>)
             expungeStaleEntry(i);
         <span style="color:#000088">else</span>
             i = nextIndex(i, len);
         e = tab[i];
     }
     <span style="color:#000088">return</span> <span style="color:#000088">null</span>;
 }
expungeStaleEntry函数的源码:

<span style="color:#000088">private</span> <span style="color:#000088">int</span> <span style="color:#009900">expungeStaleEntry</span>(<span style="color:#000088">int</span> staleSlot) {
           Entry[] tab = table;
           <span style="color:#000088">int</span> len = tab.length;
           <span style="color:#880000">// expunge entry at staleSlot</span>
           tab[staleSlot].<span style="color:#000088">value</span> = <span style="color:#000088">null</span>;
           tab[staleSlot] = <span style="color:#000088">null</span>;
           size--;
           <span style="color:#880000">// Rehash until we encounter null</span>
           Entry e;
           <span style="color:#000088">int</span> i;
           <span style="color:#000088">for</span> (i = nextIndex(staleSlot, len);
                (e = tab[i]) != <span style="color:#000088">null</span>;
                i = nextIndex(i, len)) {
               ThreadLocal<?> k = e.<span style="color:#000088">get</span>();
               <span style="color:#000088">if</span> (k == <span style="color:#000088">null</span>) {
                   e.<span style="color:#000088">value</span> = <span style="color:#000088">null</span>;
                   tab[i] = <span style="color:#000088">null</span>;
                   size--;
               } <span style="color:#000088">else</span> {
                   <span style="color:#000088">int</span> h = k.threadLocalHashCode & (len - <span style="color:#006666">1</span>);
                   <span style="color:#000088">if</span> (h != i) {
                       tab[i] = <span style="color:#000088">null</span>;
                       <span style="color:#880000">// Unlike Knuth 6.4 Algorithm R, we must scan until</span>
                       <span style="color:#880000">// null because multiple entries could have been stale.</span>
                       <span style="color:#000088">while</span> (tab[h] != <span style="color:#000088">null</span>)
                           h = nextIndex(h, len);
                       tab[h] = e;
                   }
               }
           }
           <span style="color:#000088">return</span> i;
       }</code></span>

整理一下ThreadLocalMap的getEntry函数的流程:

首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e; 
如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询 
在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。仔细研究代码可以发现,set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。 
但是光这样还是不够的,上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的getEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。

Web服务器中线程池的状态问题

Web服务在创建线程的过程中,频繁的创建线程对系能的影响巨大,故很多服务器都采用了线程池的方式来解决线程不断创建锁导致的问题,所以在使用线程池的过程中,由于线程是不断的回收和利用故ThreadLocal在服务器中也是被反复利用的,在使用中如果不进行清查操作,很容易导致变量污染。尽管对于很多服务器来说,ThreadLocal是的确是相对于每个线程,每个线程会有自己的ThreadLocal。但考虑到服务器都会维护一套线程池。因此,不同用户访问,可能会接受到同样的线程。因此,在做基于TheadLocal时,需要谨慎,避免出现ThreadLocal变量的缓存,导致其他线程访问到本线程变量,如果运用不当,会导致系统效率低下,举个例子,假设我们得系统在访问的时候在ThreadLocal中加入变量不予以更新和删除,则这个保存的对象就变成一个增量的容器对象,如果访问量巨大,将导致jvm内存不足而频繁触发gc,gc在工作的时候会进行数据复制,频繁的触发gc对系统的性能会带来不利影响,同时还有可能导致内存溢出。

遇到的问题

<span style="color:#000000"><code><span style="color:#000088">public</span> <span style="color:#000088">class</span> ContextHolder {

    <span style="color:#000088">private</span> <span style="color:#000088">static</span> ThreadLocal<UserContext> userContext = <span style="color:#000088">new</span> InheritableThreadLocal<UserContext>();

    <span style="color:#000088">public</span> <span style="color:#000088">static</span> UserContext <span style="color:#009900">getUserContext</span>(){
        <span style="color:#000088">if</span>(userContext.<span style="color:#000088">get</span>() == <span style="color:#000088">null</span>){
            userContext.<span style="color:#000088">set</span>(<span style="color:#000088">new</span> UserContext());
        }
        <span style="color:#000088">return</span> userContext.<span style="color:#000088">get</span>();
    }

    <span style="color:#000088">public</span> <span style="color:#000088">static</span> <span style="color:#000088">void</span> <span style="color:#009900">setContext</span>(UserContext context) {
        userContext.<span style="color:#000088">set</span>(context);
    }

    <span style="color:#000088">public</span> <span style="color:#000088">static</span> <span style="color:#000088">void</span> <span style="color:#009900">clear</span>(){
        userContext.remove();
    }
}</code></span>

如果每个线程(访问请求)在结束的时候没有调用clear 方法的时候,其他线程再访问,就会造成线程污染,即拿到了其他线程的变量。

参考: 
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/ 
http://blog.csdn.net/chichengit/article/details/7994712 
http://blog.csdn.net/lufeng20/article/details/24314381 
http://www.importnew.com/22039.html 
http://www.importnew.com/22046.html

 

https://blog.csdn.net/mccand1234/article/details/54173084