• ThreadLocal详解


    1.作用

    Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

    ThreadLocal最适合按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到(线程内传递数据 而不用利用方法参数显式传递)

    ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

    2.要注意的地方

    ThreadLocal并不能解决并发问题。

    ThreadLocal就像一个管理类或代理类,间接去操作真正的数据。

    真实的数据是存储在ThreadLocalMap中的,而每个线程都有一个属性threadLocals,也就是线程拥有的ThreadLocalMap。

    ThreadLocalMap内是用Entry来存储数据的,key是ThreadLocalMap实例,value就是真实的数据。

    每个线程只有一个ThreadLocalMap, 可以存多个ThreadLocal。

    线程内从ThreadLocal内获取数据时(get), 要先set,否则获取到的是null,后续自然会报NPE。当然源码也提供了initialValue方法,我们只要重写一下,自然就可以避免这个NPE了

    3.原理和源码

    ThreadLocal类核心方法set、get、initialValue、withInitial、setInitialValue、remove:

      1    /**
      2      * Returns the current thread's "initial value" for this
      3      * thread-local variable.  This method will be invoked the first
      4      * time a thread accesses the variable with the {@link #get}
      5      * method, unless the thread previously invoked the {@link #set}
      6      * method, in which case the {@code initialValue} method will not
      7      * be invoked for the thread.  Normally, this method is invoked at
      8      * most once per thread, but it may be invoked again in case of
      9      * subsequent invocations of {@link #remove} followed by {@link #get}.
     10      *
     11      * <p>This implementation simply returns {@code null}; if the
     12      * programmer desires thread-local variables to have an initial
     13      * value other than {@code null}, {@code ThreadLocal} must be
     14      * subclassed, and this method overridden.  Typically, an
     15      * anonymous inner class will be used.
     16      *
     17      * @return the initial value for this thread-local
     18      */
     19     protected T initialValue() {
     20         return null;
     21     }
     22  
     23     /**
     24      * Creates a thread local variable. The initial value of the variable is
     25      * determined by invoking the {@code get} method on the {@code Supplier}.
     26      *
     27      * @param <S> the type of the thread local's value
     28      * @param supplier the supplier to be used to determine the initial value
     29      * @return a new thread local variable
     30      * @throws NullPointerException if the specified supplier is null
     31      * @since 1.8
     32      */
     33     public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
     34         return new SuppliedThreadLocal<>(supplier);
     35     }
     36  
     37     /**
     38      * Creates a thread local variable.
     39      * @see #withInitial(java.util.function.Supplier)
     40      */
     41     public ThreadLocal() {
     42     }
     43  
     44     /**
     45      * Returns the value in the current thread's copy of this
     46      * thread-local variable.  If the variable has no value for the
     47      * current thread, it is first initialized to the value returned
     48      * by an invocation of the {@link #initialValue} method.
     49      *
     50      * @return the current thread's value of this thread-local
     51      */
     52     public T get() {
     53         Thread t = Thread.currentThread();
     54         ThreadLocalMap map = getMap(t);
     55         if (map != null) {
     56             ThreadLocalMap.Entry e = map.getEntry(this);
     57             if (e != null) {
     58                 @SuppressWarnings("unchecked")
     59                 T result = (T)e.value;
     60                 return result;
     61             }
     62         }
     63         return setInitialValue();
     64     }
     65  
     66     /**
     67      * Variant of set() to establish initialValue. Used instead
     68      * of set() in case user has overridden the set() method.
     69      *
     70      * @return the initial value
     71      */
     72     private T setInitialValue() {
     73         T value = initialValue();
     74         Thread t = Thread.currentThread();
     75         ThreadLocalMap map = getMap(t);
     76         if (map != null)
     77             map.set(this, value);
     78         else
     79             createMap(t, value);
     80         return value;
     81     }
     82  
     83     /**
     84      * Sets the current thread's copy of this thread-local variable
     85      * to the specified value.  Most subclasses will have no need to
     86      * override this method, relying solely on the {@link #initialValue}
     87      * method to set the values of thread-locals.
     88      *
     89      * @param value the value to be stored in the current thread's copy of
     90      *        this thread-local.
     91      */
     92     public void set(T value) {
     93         Thread t = Thread.currentThread();
     94         ThreadLocalMap map = getMap(t);
     95         if (map != null)
     96             map.set(this, value);
     97         else
     98             createMap(t, value);
     99     }
    100  
    101     /**
    102      * Removes the current thread's value for this thread-local
    103      * variable.  If this thread-local variable is subsequently
    104      * {@linkplain #get read} by the current thread, its value will be
    105      * reinitialized by invoking its {@link #initialValue} method,
    106      * unless its value is {@linkplain #set set} by the current thread
    107      * in the interim.  This may result in multiple invocations of the
    108      * {@code initialValue} method in the current thread.
    109      *
    110      * @since 1.5
    111      */
    112      public void remove() {
    113          ThreadLocalMap m = getMap(Thread.currentThread());
    114          if (m != null)
    115              m.remove(this);
    116      }

    createMap和getMap

     1     /**
     2      * Create the map associated with a ThreadLocal. Overridden in
     3      * InheritableThreadLocal.
     4      *
     5      * @param t the current thread
     6      * @param firstValue value for the initial entry of the map
     7      */
     8     void createMap(Thread t, T firstValue) {
     9         t.threadLocals = new ThreadLocalMap(this, firstValue);
    10     }
    11 
    12 
    13     ThreadLocalMap getMap(Thread t) {
    14         return t.threadLocals;
    15     }
    16  

    Thread.threadLocals和Thread.inheritableThreadLocals

     1 public
     2 class Thread implements Runnable {
     3     /*...其他属性...*/
     4  
     5     /* ThreadLocal values pertaining to this thread. This map is maintained
     6      * by the ThreadLocal class. */
     7     ThreadLocal.ThreadLocalMap threadLocals = null;
     8  
     9     /*
    10      * InheritableThreadLocal values pertaining to this thread. This map is
    11      * maintained by the InheritableThreadLocal class.
    12      */
    13     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    4.Thread同步机制的比较
      ThreadLocal和线程同步机制相比有什么优势呢?

      Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

      在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

      而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

      概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

      Spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

      一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。

      同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

    上述参考:枫之逆 原文:https://blog.csdn.net/lufeng20/article/details/24314381 

    5.内存泄漏问题

    ThreadLocal 实现原理

    ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例本身,value 是真正需要存储的 Object

    也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

    ThreadLocal为什么会内存泄漏

    ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现keynullEntry,就没有办法访问这些keynullEntryvalue,如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

    其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocalget(),set(),remove()的时候都会清除线程ThreadLocalMap里所有keynullvalue

    但是这些被动的预防措施并不能保证不会内存泄漏:

    • 使用staticThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏(参考ThreadLocal 内存泄露的实例分析)。
    • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。

    为什么使用弱引用

    从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?

    我们先来看看官方文档的说法:

    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
    为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

    下面我们分两种情况讨论:

    • key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
    • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,getremove的时候会被清除。

    比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除

    因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

    ThreadLocal 最佳实践

    综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?

    • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

    在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

    上面关于ThreadLocal内在泄漏的分析 摘自:http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/

    关于内存泄漏的问题他还有一篇实例分析 => ThreadLocal 内存泄露的实例分析

    6. ThreadLocal应用场景

     1、数据库连接池实现

     2、有时候ThreadLocal也可以用来避免一些参数传递,通过ThreadLocal来访问对象

     3、在某些情况下提升性能和安全,如:SimpleDateFormat

    参考:https://blog.csdn.net/u012834750/article/details/71646700


    7.关于ThreadLocal在Spring中的应用

    https://www.cnblogs.com/fishisnow/p/6396989.html

    https://www.cnblogs.com/youzhibing/p/6690341.html

  • 相关阅读:
    【漏洞分析】DDOS攻防分析(二)——HTTP篇
    【漏洞分析】DDOS攻防分析(四)——TCP篇
    [spring]spring框架的编码过滤器的使用
    [struts]s:action 的使用方法
    [maven]使用maven构建Appfuse失败
    [网站安全]windows本地安全策略阻止指定ip访问本机
    [前端]quirks模式(怪异模式)
    [前端]客户端缓存
    [struts]在线编辑器的使用选择
    [eclipse]Myeclipse console 中乱码问题
  • 原文地址:https://www.cnblogs.com/PheonixHkbxoic/p/10242044.html
Copyright © 2020-2023  润新知