1.做用html
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。java
ThreadLocal最适合按线程多实例(每一个线程对应一个实例)的对象的访问,而且这个对象不少地方都要用到(线程内传递数据 而不用利用方法参数显式传递)数据库
ThreadLocal为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本。安全
2.要注意的地方多线程
ThreadLocal并不能解决并发问题。并发
ThreadLocal就像一个管理类或代理类,间接去操做真正的数据。less
真实的数据是存储在ThreadLocalMap中的,而每一个线程都有一个属性threadLocals,也就是线程拥有的ThreadLocalMap。ide
ThreadLocalMap内是用Entry来存储数据的,key是ThreadLocalMap实例,value就是真实的数据。性能
每一个线程只有一个ThreadLocalMap, 能够存多个ThreadLocal。this
线程内从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
的实现是这样的:每一个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。
下面咱们分两种状况讨论:
ThreadLocal
的对象被回收了,可是ThreadLocalMap
还持有ThreadLocal
的强引用,若是没有手动删除,ThreadLocal
不会被回收,致使Entry
内存泄漏。ThreadLocal
的对象被回收了,因为ThreadLocalMap
持有ThreadLocal
的弱引用,即便没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用set
,get
,remove
的时候会被清除。比较两种状况,咱们能够发现:因为ThreadLocalMap
的生命周期跟Thread
同样长,若是都没有手动删除对应key
,都会致使内存泄漏,可是使用弱引用能够多一层保障:弱引用ThreadLocal
不会内存泄漏,对应的value
在下一次ThreadLocalMap
调用set
,get
,remove
的时候会被清除。
所以,ThreadLocal
内存泄漏的根源是:因为ThreadLocalMap
的生命周期跟Thread
同样长,若是没有手动删除对应key
就会致使内存泄漏,而不是由于弱引用。
综合上面的分析,咱们能够理解ThreadLocal
内存泄漏的来龙去脉,那么怎么避免内存泄漏呢?
ThreadLocal
,都调用它的remove()
方法,清除数据。在使用线程池的状况下,没有及时清理ThreadLocal
,不只是内存泄漏的问题,更严重的是可能致使业务逻辑出现问题。因此,使用ThreadLocal
就跟加锁完要解锁同样,用完就清理。
上面关于ThreadLocal内在泄漏的分析 摘自:http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/
关于内存泄漏的问题他还有一篇实例分析 => ThreadLocal 内存泄露的实例分析
6. ThreadLocal应用场景
一、数据库链接池实现
二、有时候ThreadLocal也能够用来避免一些参数传递,经过ThreadLocal来访问对象
三、在某些状况下提高性能和安全,如:SimpleDateFormat
参考:https://blog.csdn.net/u012834750/article/details/71646700
7.关于ThreadLocal在Spring中的应用