您的当前位置:首页正文

ThreadLocal随记

来源:图艺博知识网

2016-04-03 ThreadLocal ThreaLocalMap ThreadLocal reinitialized in threadpool

基本定义及解读

  • 首先,每个线程都有变量的一个本地独立副本,保证线程之间的数据不会互相影响
  • 可以通过重写initialValue()方法实现ThreadLocal的默认初始值
    protected static final ThreadLocal<String> TL_EXAMPLE =
    new ThreadLocal<String>() {
    @Override
    protected String initialValue() {
    return "default";
    }
    };
  • 为什么说明中建议定义为静态static方法呢?不了解ThreadLocal原理的同学可能就糊涂了,既然是需要满足多线程并发的,怎么会定义为一个静态的类成员变量呢?
    只要大家看一下ThreadLocal的源码就了解了,它有个静态内部类叫ThreadLocalMap<ThreadLocal, Object>, 此Map在Thread类中被定义为了一个类成员变量,即每个Thread线程中都有一个独立ThreadLocalMap副本,它的值只能被当前线程读取和修改
    想像一下某个类中定义了多个ThreadLocal<?>变量,在当前线程中通过ThreadLocalMap.get(ThreadLocal)获取到相应的变量副本。
    所以ThreadLocal变量本身不是副本,你可以把他当成一个代理,而ThreadLocalMap中存放了线程内的一个一个线程副本,ThreadLocal只是ThreadLocalMap内弱引用的Key(在ThreadLocal对象失效时可以及时的清理ThreadLocalMap)。
    这也回答了为什么ThreadLocal可以定义为static, 它只是Map中的Key而已,不同线程的Map副本获取同一个Key的值完全不会冲突。
  类ThreadLocal:  
  public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
  }
  /**
  * ThreadLocalMap is a customized hash map suitable only for
  * maintaining thread local values. No operations are exported
  * outside of the ThreadLocal class. The class is package private to
  * allow declaration of fields in class Thread.  To help deal with
  * very large and long-lived usages, the hash table entries use
  * WeakReferences for keys. However, since reference queues are not
  * used, stale entries are guaranteed to be removed only when
  * the table starts running out of space.
  */
  static class ThreadLocalMap {...}
  
  类Thread:
  /* ThreadLocal values pertaining to this thread. This map is maintained
   * by the ThreadLocal class. */
   ThreadLocal.ThreadLocalMap threadLocals = null;

Thread1 : 
       ThreadLocalMap1 : 
             <ThreadLocal1, String>
             <ThreadLocal2, Integer>
Thread2 :
       ThreadLocalMap2 :
             <ThreadLocal1, String>
             <ThreadLocal2, Integer>
  • 继续延伸出一个问题: ThreadLocal类本身是线程安全的么?
    通过源码看到不管是get,set还是createMap都没有做任何的同步或者并发锁。答案是安全的,因为实现都是基于当前线程的。

线程池与ThreadLocal变量的初始化

在线程池复用的情况下,若ThreaLocal数据没有被清理掉,会被后面的请求复用然后拿到被你修改过的值!
之前在实现日志上下文LogContext的时候碰到了类似问题:

  1. 请求A进入Controller中, 开启线程A,LogContext中记录了大量的ThreadLocal中间变量值,在请求响应结束后,请求线程A回归线程池;
  2. 请求B进入Controller中,复用线程A,LogContext会在之前变量值的基础上继续添加信息,这样的日志信息成了叠加的了。

不管是基于自己实现的线程池,还是应用服务器(如Tomcat)的线程池,都需要小心这一点!
标准或者规范做法是在线程变量使用完毕之后,或者finally代码块中调用 threadLocalVariable.remove() 移除,以防被其他线程复用。

Top