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

posted @ 2019-01-08 23:27  PheonixHkbxoic  阅读(584)  评论(0编辑  收藏  举报