Java ThreadLocal
开篇
ThreadLocal
是 JDK底层提供的一个解决多线程并发问题提供的工具类,它为每个线程提供了一个本地的副本变量机制,实现了和其它线程隔离,并且这种变量只在本线程的生命周期内起作用,可以减少同一个线程内多个方法之间的公共变量传递的复杂度。
举一个比较形象的例子(自己想的,说的不好请多多指正):中学时期,我们经常会有这样一个场景:老师把布置的作业写到黑板上,没有ThreadLocal
这种机制的话是这样的,有一个很大的作业本,每个学生把自己的作业都写到这一个笔记本上面属于自己的那一块区域上。而我们也知道,实际的场景是每个同学都有一个自己的作业本,把各自的作业抄写到自己的作业本上面,就实现了和其它同学的作业隔离,感觉这个比较像ThreadLocal
的工作原理。
[TOC]
1. ThreadLocal 应用
1.1 ThreadLocal 使用场景
开篇的时候我们大致知道了 ThreadLocal
是个啥,然后这部分了解下 ThreadLocal
的使用场景:
它并不是为了解决多线程共享变量的问题,比如商品的库存数量这种场景下是不能使用 ThreadLocal
的。ThreadLocal
是多线程都需要使用一个变量,但是这个变量的值不需要各个线程间共享,每个线程都有自己的这个变量的值。ThreadLocal
还有一种场景是 在 API
层,我们经常需要 request
这个参数,我们可能就需要在很多场景下使用这个参数,但是每个方法都把它作为参数的话会让方法的参数过多不好维护,所以我们可以把这些 request
都对应到一个线程上面,一个线程内如果想使用这个参数,直接去取就行了。
简而言之就是每个线程拥有自己的实例,然后实例需要在对应线程的使用的多个方法中共享但是不希望被多线程共享。
ThreadLocal
主要解决2类问题:
- 并发问题:使用
ThreadLocal
代替Synchronized
来保证线程安全,同步机制采用空间换时间 -> 仅仅先提供一份变量,各个线程轮流访问,后者每个线程都持有一份变量,访问时互不影响。 - 数据存储问题:
ThreadLocal
为变量在每个线程中创建了一个副本,所以每个线程可以访问自己内部的副本变量。
1.2 ThreadLocal 在 Spring 中的使用->解决线程安全问题
一般情况下,只有无状态的 Bean
才会在各个实例中共享,在 Spring
中绝大多数的 Bean
都可以声明为 singleton
单例的,比如一些 request
相关的 非线程安全状态采用了 ThreadLocal
让它们成为线程安全的状态。一般情况下,web 应用划分成 MVC 三层,在不同的层次中编写对应的逻辑,下层通过接口向上层开放功能调用,正常情况下,从接收请求到响应都应该属于同一个线程。而 ThreadLocal
是一个很好的机制,它为每个线程提供了一个独立的变量副本解决了变量并发访问的冲突问题,比 Synchronized
要简单且方便,可以让程序具备更高的并发性.
1.3 以HttpRequest为例说明项目中如何使用ThreadLocal的代码
// 定义一个全局的Filter
public class CommonFilter extends OncePerRequestFilter {
/**
* 拦截所有的http请求,需要配置过滤器
* @param request
* @param response
* @param filterChain
* @throws ServletException
* @throws IOException
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
// 把requesu塞到线程的ThreadLocal中
RequestManager.setHttpServletRequest(request);
filterChain.doFilter(request, response);
} finally {
// 擦除value为null的防止内存泄露
RequestManager.removeHttpServletRequest();
}
}
}
// http请求管理
public class RequestManager {
private static ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<HttpServletRequest>();
/**
* 当前线程加入request
* @param request
*/
public static void setHttpServletRequest(HttpServletRequest request){
if(request != null){
threadLocal.set(request);
}
}
/**
* 当前线程获取request,在API接口中可以直接调用这个方法获取当前线程的request对象
*/
public static HttpServletRequest getHttpServletRequest(){
return threadLocal.get();
}
/**
* 清理request,释放空间
*/
public static void removeHttpServletRequest(){
threadLocal.remove();
}
}
// Test
@RestController
public class Demo {
@RequestMapping("/testDemo")
public void test(String s){
/**
* 通过这种方式就可以把请求取出来了,不用每次都在参数上加一个request了
*/
HttpServletRequest request = RequestManager.getHttpServletRequest();
}
}
2. ThreadLocal 的实现原理
2.1 ThreadLocal 的结构
ThreadLocal
主要分为2个部分:第一部分是它的一些成员属性,这部分主要和计算哈希值相关的。另一部分是它对外提供的几个API,这些方法可以操作它自己内部非常重要的内部类 ThreadLocalMap
所以说它才是 ThreadLocal
的底层实现。
通过前面基本知道了怎么使用 ThreadLocal
了,并且知道了它可以为每个线程提供一个局部的变量副本,实现了线程之间的数据隔离,提高程序的并发性等,但是我们并不知道它是如何实现这部分的功能的。所以这部分开始读码了解底层原理,在了解原理之前,得先知道 Thread ThreadLocal ThreadLocalMap
之间的关系。概括一下: ThreadLocal 并不是把 Thread 作为 key 副本值作为 value的一种类似 HashMap 的结构。而是每个 Thread
里都有一个 ThreadLocalMap
,ThreadLocal
只是操作每个线程的 ThreadLocalMap 而已。
不同的版本下的 ThreeadLocal
:
早期的 ThreadLocal
是每个 ThreadLocal
类都会去创建一个 Map
,然后以线程 id作为 key
,要存储的局部变量作为 value
,这样就可以达到线程隔离的效果。但是这样的话,这个存储数量是 Thread
的数量决定,当线程销毁之后还要去维护 Map
中的那份 k-v
让它也随之销毁。后来的版本是这么设计的:每个线程都维护一个 ThreadlocalMap
哈希表(类似HashMap),这个哈希表的 key
是 ThreadLocal
对象本身,value
是要存储的局部副本值,这样的话存储数量是 ThreadLocal
的数量决定的。当 Thread
销毁之后,ThreadLocalMap
也会被随之销毁,减少内存占用。
而ThreadLocalMap
的实现原理跟HashMap
差不多,内部有一个 Entry
数组,一个 Entry
通常至少包括key,value
, 特殊的是这个Entry继承了 WeakReference
也就是说它是弱引用的所以可能会有 内存泄露 的情况。这个后面再说。ThreadLocal
负责管理 ThreadLocalMap
,包括插入,删除 等等.另一方面来说 ThreadLocal
基本上就相当于 门面设计模式中的一个Facade类
。key就是 ThreadLocal
对象自己,同时,很重要的一点:就ThreadLocal
把 Map
存储在当前线程对象里面。
2.2 ThreadLocal 的成员属性
public class ThreadLocal<T> {
/**
* 自定义哈希码(ThreadLocalMaps的),降低哈希冲突
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 生成下一个哈希码的hashCode,操作是原子的,从0开始
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* 连续分配的两个ThreadLocal实例的threadLocalHashCode值的增量
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 返回下一个哈希码的hashCode,此方法是一个原子类不停地去加上斐波那契散列数,使得哈希值分布均匀
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
2.3 ThreadLocal 的方法
ThreadLocal定义了四个主要的方法,
set get remove initalValue
对于ThreadLocal有2点需要注意:
ThreadLocal
实例本身是不存储值,它只是提供了一个在当前线程中找到副本值的key(它自己就是ThreadLocalMap的key)
2.是ThreadLocal
包含在Thread中,而不是Thread包含在ThreadLocal
中,有些小伙伴会弄错他们的关系。
get()
操作获取ThreadLocal
中对应的当前线程存储的值的时候,先得到当前线程的Thread
对象,进而获取此线程对象中维护的ThreadLocalMap
,然后判断ThreadLocalMap
是否存在,如果存在,以当前的ThreadLocal
为key
,调用ThreadLocalMap
的getEntry()
方法获取对应的存储实体 e ,找到对应的value值,也就是我们想要的此线程对应的ThreadLocal
值,然后返回。
/**
* 此方法第一次调用发生在当线程通过 {@link #get} 方法访问此线程的ThreadLocal值时
* 除非线程先调用了 {@link #set},在这种情况下,{@code initialValue} 方法才不会被这个线程调用
* 通常情况下,每个线程最多调用1次这个方法,但是也可能再次调用,比如 {@link #remove} 被调用后,调用get
* 这个方法仅仅简单返回null{@code null}; 如果程序想要它返回除了null之外的初始值,必须继承重写此方法,
* 通常使用匿名内部类的方式实现
* @return 返回当前ThreadLocal的初始值 null
*/
protected T initialValue() {
return null;
}
/**
* 返回当前线程中保存ThreadLocal的值
* 如果当前线程没有此ThreadLocal则通过 {@link #initialValue}方法进行初始化值
* @return
*/
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的`ThreadLocalMap`对象
ThreadLocalMap map = getMap(t);
// 如果Map存在
if (map != null) {
// 以当前ThreadLocal实例对象为key获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 找到对应的存储实体e
if (e != null) {
@SuppressWarnings("unchecked")
// 获取e对应的value值,也就是我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
// 如果map不存在,证明此线程没有维护此ThreadlocalMap对象,进行一波初始化操作
return setInitialValue();
}
/**
* set的变样实现,用于初始化值initialValue
* 用来代替防止用户重写set而无法初始化
* @return 返回初始化后的值
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 如果此map村咋洗,调用map,set设置此实体entry
if (map != null)
map.set(this, value);
else
// map不存在时,调用此方法进行ThreadLocalMap对象初始化并将此entry作为第一个值放进去
createMap(t, value);
// 返回设置的value值s
return value;
}
/**
* 设置此线程对应的ThreadLocal的值,大多数子类不需要重写此方法,
* 只需要依赖{@link #initialValue} 方法代替设置当前线程对应的ThreadLocal值
* @param 将要保存在当前线程对应的ThreadLocal值
* 1. 获取当前线程`Thread`对象,进而获取此线程对象中维护的`ThreadLocalMap`对象
*2. 判断当前的`ThreadLocalMap`是否存在,如果存在就直接调用map.set设置entry,如果不存在就调用`createMap`进行`ThreadLocalMap`对象的初始化,并将此实体`entry`作为第一个值存放到`ThreadLocalMap`中。
*/
public void set(T value) {
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
// 存在就设置此entry
if (map != null)
map.set(this, value);
else
// 不存在就进行对象初始化并设置entry作为第一个值存入ThreadLocalMap中
createMap(t, value);
}
/**
* 删除当前线程中保存ThreadLocal对应的实体entry
* 如果此ThreadLocal变量在当前线程中调用{@linkplain #get read} 方法
* 则会通过调用{@link #initialValue} 方法进行初始化
* 除非此值value是通过当前线程内置调用set方法设置
* 这可能导致在当前线程中多次调用initialValue方法初始化
* 1. 获取当前线程Thread对象,进而获取此线程对象中维护的ThreadLocalMap对象。
* 2. 判断`ThreadLocalMap`是否存在,如果存在,调用map.remove,以当前的`ThreadLocal`为key删除对应的`entry`
*/
public void remove() {
// 获取当前线程Thread对象,进而获取此线程对象中维护的`ThreadLocalMap`对象
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 如果`ThreadLocalMap`存在调用remove方法删除之,当前ThreadLocal对象为key
m.remove(this);
}
/**
* 获取当前对象Thread对应维护的ThreadLocalMap
* @param 当前线程
* @return 对应的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
2.4 ThreadLocal的内部类ThreadLocalMap
ThreadLocalMap
其内部利用 Entry
来实现 key-value
的存储,类似 HashMap
的结构 如下代码,从上面代码中可以看出 Entry
的 key
就是 ThreadLocal
,而value
就是值。同时,``Entry 也继承
WeakReference` ,所以说Entry所对应key(ThreadLocal实例)的引用为一个弱引用,弱引用相关的在总结里有。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap
的源码稍微多了点,我们就看两个最核心的方法
getEntry(ThreadLocal<?> key)
set(ThreadLocal> key, Object value)
这个 set()
操作和我们在集合了解的 put()
方式有点不一样,虽然他们都是 key-value
结构,不同在于他们解决散列冲突的方式不同。集合Map的put()采用的是拉链法,而ThreadLocalMap的set()则是采用开放定址法,开放地址法就是不会有链式的结构,如果冲突了,以当前位置为基准再找一个判断,直到找到一个空的地址。set()操作除了存储元素外,还有一个很重要的作用,就是replaceStaleEntry()和cleanSomeSlots(),这两个方法可以清除掉key == null 的实例,防止内存泄漏。
get()方法有一个重要的地方当key == null时,调用了expungeStaleEntry()方法,该方法用于处理key == null,有利于GC回收,能够有效地避免内存泄漏。
private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
// 根据 ThreadLocal 的散列值,查找对应元素在数组中的位置
int i = key.threadLocalHashCode & (len-1);
// 采用“线性探测法”,寻找合适位置
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// key 存在,直接覆盖
if (k == key) {
e.value = value;
return;
}
// key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
if (k == null) {
// 用新元素替换陈旧的元素
replaceStaleEntry(key, value, i);
return;
}
}
// ThreadLocal对应的key实例不存在也没有陈旧元素,new 一个
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
// cleanSomeSlots 清楚陈旧的Entry(key == null)
// 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**用了开放定址法,所以当前key的散列值和元素在数组的索引并不是完全对应的,首先取一个探测数(key的散列值),如果所对应的key就是我们所要找的元素,则返回,否则调用getEntryAfterMiss(),如下:*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
8. 总结
- 问题1:
ThreadLocalMap
中存储实体Entry
使用当前threadLocal
实例作为key,但是这个Entry
继承了弱引用WeakReference
,什么是弱引用?为什么要这么设计?这样会带来什么问题?
先说下什么是强引用,什么是弱引用:
a. 在正常的情况下,使用的普遍是强引用,比如A a = new A();
这样创建一个a
这个对象,当我们执行a = null;
的时候,过一段时间GC会把分配给a
的空间回收掉。
b. 基于上面已经有了 A a = new A();
,又出现了一个B b = new B(a);
这样一个创建对象b
这个操作。此时我们执行a = null;
这个操作,GC并不会回收分配给a
的空间,因为即使a
被设置为null
,但是b
仍然持有对象a
的引用,所以GC
不会回收a
,这样一来就尴尬了 既不能回收,又不能使用 这种情况就有一个专业的名词叫内存泄露
。
那么如何处理呢?可以通过b = null;
,也可以使用弱引用WeakReference w = new WeakReference(a)
;。因为使用了弱引用WeakReference
,GC是可以回收 a
原先所分配的空间的。
再回到 ThreadLocalMap
的层面来看为啥哈希表的节点要实现WeakReference
弱引用。也就是ThreadLocalMap
中的key使用Threadlocal
实例作为弱引用。如果一个ThreadLocal
没有外部引用去引用它,那么在系统GC
的时候它势必要被回收的。这样一来ThreadLocalMap
中就会出现key
为null
的entry
就没有办法访问这些key
为null
的Entry
的value
。如果线程一直不能结束的话,就会存在一条强引用链:ThreadLocalRef->Thread->ThreadLocal->ThreadLocalMap->Entry->value
永远无法被回收造成内存泄露。其实在ThreadLocalMap
的设计中为了防止这种情况,也有一些防护措施,比如新增、移除、获取的时候都会去擦除key==null
的value
。但是这些措施并不能保证一定不会内存泄露,比如:
a. 使用了static
修饰的ThreadLocal
,延长了ThreadLocal
的生命周期,可能会导致内存泄露。
b. 分配使用了ThreadLocal
又不再调用get set remove
方法也会导致内存泄露。
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal
使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
官方给的说法是: 为了应对非常大和长时间的用途,哈希表使用弱引用的 key。
我们假设我们自己设计的时候key 使用的强引用和弱引用
-
key
使用强引用:如果引用ThreadLocal
的对象ThreadLocalRef
被回收了,但是ThreadLocalMap
还持有ThreadLocal
对象的强引用,如果没有手动删除的话ThreadLocal
不会被回收,这样会导致Entry
内存泄露 -
key
使用弱引用:引用的ThreadLocal
的对象ThreadLocalRef
被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用get(),set(),remove()
的时候会被清除。
比较上面的2种情况,我们会发现:ThreadLocalMap
的生命周期和Thread
一样长,如果都没有手动删除key
都会导致内存泄露。但是弱引用多了一层保障,就是value
在下一次ThreadLocalMap
调用get(),set(),remove()
的时候会被清除。
因此可知,ThreadLocal
发生内存泄露的根源是由于ThreadLocal
的生命周期和Thread
一样长,在没有手动删除对应的key
的时候就会导致内存泄露,并不是因为弱引用导致的,弱引用只是优化的方式。
综上分析:为了避免内存的泄露,每次使用完ThreadLocal
的时候都需要调用remove()
方法来擦除数据。并且大规模网站一般都会使用到线程池,如果没有及时清理的话不仅是内存泄露,业务逻辑可能也会被影响。所以养成好习惯,记得擦除数据。
- 问题2:说一说
ThreadLocal
和synchronized
的区别?
ThreadLocal
和synchronized
都是用来处理多线程环境下并发访问变量的问题,只是二者处理的角度不同、思路不同。
ThreadLocal
是一个类,通过对当前线程中的局部变量操作来解决不同线程的变量访问的冲突问题。所以ThreadLoca
l提供了线程安全的共享对象机制,每个线程都拥有其副本。
Java中的synchronized
是一个保留字,它依靠JVM的锁机制来实现临界区的函数或者变量的访问中的原子性。在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。此时,被用作“锁机制”的变量时多个线程共享的。
同步机制(synchronized关键字)采用了以“时间换空间”的方式,提供一份变量,让不同的线程排队访问。而ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不影响。
-
问题3:
ThreadLocal
实现原理是什么?它是怎么样做到局部变量不同的线程之间不会相互干扰的?
最开始的时候,我还没有去看源码只是知道它的一些功能的时候,依照ThreadLocal
的表现,我猜的是这样的:每个ThreadLocal
类都去创建一个Map,然后用线程ID作为key,要存储的局部变量为Value,这样就可以实现线程的值互相隔离的效果。第一次看JDK8的源码的时候才知道我完全理解反了,这是初期的时候的思想。
现在的底层是每个Thread
维护了一个ThreadLocalMap
哈希表,这个哈希表key是ThreadLocal
实例本身,value
是真正要存储的值Object
。既然JDK8对这样优化的话一定是有原因的:- 这样设计之后每个Map存储的Entry数量就会变小,因为之前存储数量是由
Thread
的数量决定的,现在是由ThreadLocal
的数量决定的 - 当
Thread
销毁之后,对应的ThreadLocalMap
也会随之销毁,减少内存的占用
- 这样设计之后每个Map存储的Entry数量就会变小,因为之前存储数量是由
-
问题4:原理总结
- 每个Thread维护着一个ThreadLocalMap的引用
- ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
- 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
- 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
- ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
正是因为这几点,所以能够实现数据隔离,获取当前线程的局部变量值,和其它线程无关。
- 问题5:线程之间如何传递
ThreadLocal
对象?
在实际开发中,我们经常会使用 ThreadLocal
传递日志的 requestId
,以此来获取整条的请求链路记录下来方便排查问题。然而当一个线程中开启了其它的线程,此时的 Threadlocal
里面的数据就会无法获取。比如下面的代码最开始获取到的就是Null。因为不是同一个线程,所以理所当然输出的值为Null,如果要实现父子线程通信,这个问题在 Threadlocal
的子类 InheritableThreadLocal
已经有对应的实现了,通过这个实现,可以实现父子线程之间的数据传递,在子线程中能够使用父线程的 ThreadLocal
本地变量。InheritableThreadLocal
继承了 ThreadLocal
并且重写了三个相关的方法,具体处理大致是 之前的 ThreadLocal
获取 ThreadlocalMap
的时候一般都是用 this
,在这里都是Thread先获取父线程,然后将父线程的 ThreadLocalMap
传递给子线程
/**
* ThreadLocalTest
*
* @author yupao
* @since 2019/1/22 下午10:52
*/
public class ThreadLocalTest {
public static void main(String[] args) {
//ThreadLocal<String> threadLocal = new ThreadLocal<>();
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("main thread");
Thread thread = new Thread(()->{
// 直接使用ThreadLocal输出 null,使用ThreadLocal输出 main thread
System.out.println(threadLocal.get());
});
thread.start();
}
}
真的这么好的吗,看下阿里巴巴编码规范插件说了啥?不要显示创建线程,请使用线程池!所以下面我们用线程池来试试!
不规范提示
如下代码所示,当线程池的核心线程数设置为1的时候,2次输出的结果都是 ”我是主线程1“。ThreadPoolManage
是我本地写的一个线程池实现,github上有源码。原因相信都能踩到了,线程池会缓存使用过的线程,第一个任务来的时候创建一个线程,此时线程空闲了,第二次来任务还是会使用这个线程,所以就会出现下面的问题了。如何解决?阿里的transmittable-thread-local
提供了解决方案,思路是,InheritableThreadLocal虽然可以完成父子线程的传递,但是对于使用了线程池的情况线程是让线程池去创建好的,然后拿来复用的,这个时候父子线程传递 ThreadLocalMap
的引用没有意义了,应用需要的是吧任务提交给线程池时候把 ThreadLocalMap
传递到任务去执行。感兴趣在阿里的github上有,已经开源的。
/**
* ThreadLocalTestExecutor
*
* @author yupao
* @since 2019/1/23 下午11:00
*/
public class ThreadLocalExecutorTest {
private static ThreadPoolManager threadPoolManager = ThreadPoolManager.INSTANCE;
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
threadLocal.set("我是主线程1");
threadPoolManager.addExecuteTask(()->{
System.out.println(threadLocal.get());
return null;
});
threadLocal.set("我是主线程2");
threadPoolManager.addExecuteTask(()->{
System.out.println(threadLocal.get());
return null;
});
//当线程池核心线程数为1的时候2次输出都是 我是主线程1
}
}
- 问题6:
InheritableThreadLocal
是如何弥补ThreadLocal
不支持继承的特性,它的实现原理是啥?
InheritableThreadLocal 在子线程创建的时候把父线程的ThreadLocalMap
传递给它,它继承ThreadLocal
并重写了3个方法,并使用Thread.inheritableThreadLocals
代替了Thread.threadlocals
字段
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}