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中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是这些被动的预防措施并不能保证不会内存泄漏:
- 使用
static的ThreadLocal,延长了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,get,remove的时候会被清除。
比较两种情况,我们可以发现:由于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中的应用

浙公网安备 33010602011771号