土味情话:推荐一个0卡又很甜的零食,我的嘴巴
前言
首先想一下ThreadLocal
的应用场景是什么呢?
一般来说,
ThreadLocal
在项目中用来保存用户的信息(例如session
等)将当前用户和当前线程绑定,可以在同一个线程中的任何地方获取到该信息。
那么ThreadLocal
的缺点是什么呢?
刚才说到
ThreadLocal
将数据和当前线程绑定,所以呢?没错,如果不是同一个线程,那么无法获取到其他线程的数据。而InheritableThreadLocal
就是用来解决这个问题
###ThreadLocal怎么保存数据的?
-
ThreadLocal的set方法
看看
set
方法到底是如何保存数据,如何做到线程独享public void set(T value) { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取当前线程维护的`ThreadLocalMap`对象(可以认为是map) ThreadLocalMap map = getMap(t); if (map != null) // 直接保存 map.set(this, value); else // 创建`ThreadLocalMap`之后再保存 createMap(t, value); } 复制代码
其实,看到这里就已经可以了,每个
value
是保存在每个线程维护的ThreadLocalMap
对象中和每个线程是绑定的,所以看似是调用的同一个ThreadLocal
对象来保存数据,其实在并不是将数据保存在ThreadLocal
中,而是保存在Thread
维护的ThreadLocalMap
中,做到的资源隔离。 -
多线程中无法共享数据
所以,在A线程中保存的数据,可以在A线程中任意获取。但是如果这个时候新开了一个B线程,B线程中肯定就获取不到A线程中保存的数据。因为是两个不同的线程,在获取数据的时候需要获取到当前线程
Thread
对象,然后获取当前线程维护的ThreadLocalMap
,然后再获取指定的数据。Thread
都不是一个,所以当然也获取不到其他线程的数据啦看看
ThreadLocal
的get
方法就明白了public T get() { // 首先获取当前线程对象 Thread t = Thread.currentThread(); // 获取当前线程对象维护的`ThreadLocalMap` ThreadLocalMap map = getMap(t); if (map != null) { // 从`ThreadLocalMap`中获取当前key(threadLocal对象)对应的value ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } return setInitialValue(); } 复制代码
InheritableThreadLocal怎么传递数据的?
InheritableThreadLocal
字面意思为:可被继承的ThreadLocal,所以作用也很明显,可以让子线程拥有父线程ThreadLocal
中的数据,相当于共享。不过这个共享是一次性的,如果父线程更新了ThreadLocal的值,那么更新的数据不会实现同步
-
父子线程共享数据示例代码
private static InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>(); @Test public void asyncInherriableThreadLocal() { // 在main线程中向InheritableThreadLocal中保存数据 inheritableThreadLocal.set(300); // 在子线程1中获取InheritableThreadLocal中的数据 new Thread(() -> { System.out.println("future1---inheritableThreadLocal:" + inheritableThreadLocal.get()); }).start(); TimeUnit.SECONDS.sleep(1); System.out.println("main----inheritableThreadLocal:" + inheritableThreadLocal.get()); } 复制代码
控制台输出:
future1---inheritableThreadLocal:300 main----inheritableThreadLocal:300 复制代码
在
main
线程中保存的数据可以在线程1
中获取到,表示通过InheritableThreadLocal
父子线程可以共享数据 -
如何实现父子线程共享数据
-
创建线程的过程中发生了什么?
- 第一步,
new Thread()
中调用init()
private void init(ThreadGroup g, Runnable target, String name, long stackSize) { // 注意最后一个参数`true`,默认是开启InheritThreadLocal init(g, target, name, stackSize, null, true); } 复制代码
- 第二步,判断是否开启
dd
,然后执行创建当前线程的ThreadLocalMap
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ... if (inheritThreadLocals && parent.inheritableThreadLocals != null) // 创建当前线程的`ThreadLocalMap`对象,并通过父线程中维护的`inheritableThreadLocals`该变量即`InheritableThreadLocal`对象 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ... } 复制代码
- 第三步,将父线程的
ThreadLocalMap
中的数据复制一份到子线程的ThreadLocalMap
(这也是InheritableThreadLocal可以共享数据的原因)
private ThreadLocalMap(ThreadLocalMap parentMap) { // 获取到父线程的`ThreadLocalMap`中的Entry数组 Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; // 遍历父线程中的`ThreadLocalMap`中的Entry数组 for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { // 获取父线程的值 Object value = key.childValue(e.value); // 创建一个新的Entry对象,用于保存到`ThreadLocalMap` Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); // 保存到当前线程的`ThreadLocalMap`维护的`Entry[]`中,实现数据传递 table[h] = c; size++; } } } } 复制代码
- 第一步,
第三步就是
InheritableThreadLocal
是怎么传递数据从当前线程到子线程,实现数据共享的,也是为什么后续更新数据,子线程不会同步更新数据的原因。 -
小总结
-
如何实现数据传递到子线程中?
当前线程中创建子线程的时候,默认会将当前线程中的
ThreadLocalMap
进行遍历,将里面的所有数据都复制一份保存到创建的新线程的ThreadLocalMap
-
为什么不能同步更新?
因为
InheritableThreadLocal
实现数据传递并不是使用一个容器,可以复制一份数据到新创建的线程,所以只能同步创建时刻的数据,无法同步更新数据。
微信公众号「指尖上的代码」,欢迎关注~
你的点赞和关注是写文章最大的动力~