Threadlocal
ThreadLocal 提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。
ThreadLocal
是用来处理多线程并发问题的一种解决方案。在多线程并发环境下,提供了与其他线程隔离的局部变量。
在 ThreadLocal
类中有一个静态内部类 ThreadLocalMap
,每一个线程 Thread
持有一个 ThreadLocalMap
,键为该线程对象,而值对应线程的变量副本。
void set(T value)
中 ThreadLocalMap map = Thread.currentThread().threadLocals;
|
|
ThreadLocal 源码
public void set(T value)
设置当前线程的线程局部变量的值
- 获得当前线程对象
Thread
- 取出当前线程对象的成员变量
ThreadLocalMap
- key 是
ThreadLocal
,value是设置的值存放到ThreadLocalMap
public T get()
返回当前线程所对应的线程局部变量
- 获得当前线程对象
Thread
- 取出当前线程对象的成员变量
ThreadLocalMap
ThreadLocalMap
中获取当前线程对应的值,如果为null,返回private T setInitialValue()
其实就是通过重载T initialValue()
获取的值
public void remove()
将当前线程局部变量的值删除
目的是为了减少内存的占用,该方法是JDK5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected T initialValue()
返回该线程局部变量的初始值
该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
ThreadLocal 副作用
虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象,这个时候ThreadLocal就失效了。
Thread 数量一般来说比ThreadLocal数量多,所以 ThreadLocalMap 放在Thread 中
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在 ThreadLocalMap
Entry<null, Object>的键值对,造成内存泄露。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,有两种手段。
- 使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
- JDK 建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
ThreadLocalMap
中的 key 使用 ThreadLocal
的弱引用,但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。也可以通过调用ThreadLocal的remove方法进行释放!
- WeakReference 的引入,是为了将ThreadLocal 对象与ThreadLocalMap 设计成一种弱引用的关系,来避免ThreadLocal 实例对象不能被回收而存在的内存泄露问题,当threadLocal 对象被回收时,会有清理 stale entry 机制,回收其对应的Value实例对象。
- 我们常说的内存泄露问题,针对的是threadLocal对应的Value对象实例。在线程对象被重用且threadLocal为静态变量时,如果没有手动remove(),就可能会造成内存泄露的情况。
- 上述两种内存泄露的情况只有在线程复用的情况下才会出现,因为在线程销毁时threadLocalMap的对象引用会被置为null。
- 解决副作用的方法很简单,就是每次用完ThreadLocal,都要及时调用 remove() 方法去清理。
使用
|
|
InheritableThreadLocal
在一些场景中,子线程需要可以获取父线程的本地变量,比如用一个统一的ID来追踪记录调用链路。但是ThreadLocal 是不支持继承性的,同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到对应的对象的。 为了解决这个问题,InheritableThreadLocal 也就应运而生。
|
|
这个类能让子线程继承父线程中已经设置的ThreadLocal值。
需要注意的是一旦子线程被创建以后,再操作父线程中的ThreadLocal变量,那么子线程是不能感知的。因为父线程和子线程还是拥有各自的ThreadLocalMap,只是在创建子线程的“一刹那”将父线程的ThreadLocalMap复制给子线程,后续两者就没啥关系了。
ThreadLocal在Spring中发挥着重要的作用,在管理request作用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的作用。
在Spring 事务管理中的应用
在事务管理中,在服务类中的涉及到事务的方法,每个事务的上下文都应该是独立拥有数据库的 Connection连接的,否则在数据提交回滚过程中就会产生冲突。
Spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。
DataSourceTransactionManager的实现中,doBegin()方法开启事务。
首先从数据库连接池中获得一个Connection
实例,并构造一个 ConnectionHolder
包装类实例,使用这个包装类开启事务,最后通过 TransactionSynchronizationManager.bindResource()
将Connection 实例与ThreadLocal绑定,事务提交或者回滚后,解除绑定 unbindResource()
。
|
|
小结
ThreadLocal 实现线程内部变量共享,InheritableThreadLocal 实现了父线程与子线程的变量继承。但是还有一种场景,InheritableThreadLocal 无法解决,也就是在使用线程池等会池化复用线程的执行组件情况下,异步执行执行任务,需要传递上下文的情况。
针对上述情况,阿里开源了一个TTL库,即 Transmittable ThreadLocal 来解决这个问题。