转载:https://github.com/pzxwhc/MineKnowContainer/issues/20

介绍 InheritableThreadLocal 之前,假设对 ThreadLocal 已经有了一定的理解,比如基本概念,原理,如果没有,可以参考:前面两篇博文

这里再复习下 ThreadLocal 的原理,因为会对 InheritableThreadLocal 的理解 有重大的帮助:

  1. 每个线程都有一个 ThreadLocalMap 类型的 threadLocals 属性。
  2. ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。
  3. 当我们通过 threadLocal.set(new Integer(123)); ,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123)); 的 threadlocal,value 就是值。
  4. 当我们通过 threadlocal.get() 方法的时候,首先会根据这个线程得到这个线程的 threadLocals 属性,然后由于这个属性放的是键值对,我们就可以根据键 threadlocal 拿到值。 注意,这时候这个键 threadlocal 和 我们 set 方法的时候的那个键 threadlocal 是一样的,所以我们能够拿到相同的值。

Ps:如果这个原理没搞清楚,那么下文估计有比较难理解,所以建议完完全全搞懂这个原理。

InheritableThreadLocal 概念

从上面的介绍我们可以知道,我们其实是根据 Thread.currentThread(),拿到该线程的 threadlocals,从而进一步得到我们之前预先 set 好的值。那么如果我们新开一个线程,这个时候,由于 Thread.currentThread() 已经变了,从而导致获得的 threadlocals 不一样,我们之前并没有在这个新的线程的 threadlocals 中放入值,那么我就再通过 threadlocal.get()方法 是不可能拿到值的。例如如下代码:

  1. public class Test {
  2. public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
  3. public static void main(String args[]){
  4. threadLocal.set(new Integer(123));
  5. Thread thread = new MyThread();
  6. thread.start();
  7. System.out.println("main = " + threadLocal.get());
  8. }
  9. static class MyThread extends Thread{
  10. @Override
  11. public void run(){
  12. System.out.println("MyThread = " + threadLocal.get());
  13. }
  14. }
  15. }

输出是:

  1. main = 123
  2. MyThread = null

那么这个时候怎么解决? _InheritableThreadLocal 就可以解决这个问题。_ 先看一个官方对它的介绍:

  1. * This class extends <tt>ThreadLocal</tt> to provide inheritance of values
  2. * from parent thread to child thread: when a child thread is created, the
  3. * child receives initial values for all inheritable thread-local variables
  4. * for which the parent has values. Normally the child's values will be
  5. * identical to the parent's; however, the child's value can be made an
  6. * arbitrary function of the parent's by overriding the <tt>childValue</tt>
  7. * method in this class.

也就是说,我们把上面的

public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();

改成

public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

再运行,就会有结果:

  1. main = 123
  2. MyThread = 123

也就是子线程或者说新开的线程拿到了该值。_那么,这个究竟是怎么实现的呢,key 都变了,为什么还可以拿到呢?_

InheritableThreadLocal 原理

我们可以首先可以浏览下 InheritableThreadLocal 类中有什么东西:

  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2. protected T childValue(T parentValue) {
  3. return parentValue;
  4. }
  5. ThreadLocalMap getMap(Thread t) {
  6. return t.inheritableThreadLocals;
  7. }
  8. void createMap(Thread t, T firstValue) {
  9. t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
  10. }
  11. }

其实就是重写了3个方法。

首先,当我们调用 get 方法的时候,由于子类没有重写,所以我们调用了父类的 get 方法:

  1. public T get() {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null) {
  5. ThreadLocalMap.Entry e = map.getEntry(this);
  6. if (e != null) {
  7. @SuppressWarnings("unchecked")
  8. T result = (T)e.value;
  9. return result;
  10. }
  11. }
  12. return setInitialValue();
  13. }

这里会有一个Thread.currentThread() , getMap(t) 方法,所以就会得到这个线程 threadlocals。 但是,由于子类 InheritableThreadLocal 重写了 getMap()方法,再看上述代码,我们可以看到:
_其实不是得到 threadlocals,而是得到 inheritableThreadLocals。_ inheritableThreadLocals 之前一直没提及过,其实它也是 Thread 类的一个 ThreadLocalMap 类型的 属性,如下 Thread 类的部分代码:

  1. ThreadLocal.ThreadLocalMap threadLocals = null;
  2. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

那么,这里看 InheritableThreadLocal 重写的方法,感觉 inheritableThreadLocals 和 threadLocals 几乎是一模一样的作用,只是换了个名字而且,那么究竟 为什么在新的 线程中 通过 threadlocal.get() 方法还能得到值呢?

这时候要注意 childValue 方法,我们可以看下它的官方说明:

  1. * Computes the child's initial value for this inheritable thread-local
  2. * variable as a function of the parent's value at the time the child
  3. * thread is created. This method is called from within the parent
  4. * thread before the child is started.

这个时候,你明白了,是不是在 创建线程的时候做了手脚,做了一些值的传递,或者这里利用上了 inheritableThreadLocals 之类的。

其实,是的:

  • _关键在于 Thread thread = new MyThread();_
  • _关键在于 Thread thread = new MyThread();_
  • _关键在于 Thread thread = new MyThread();_

这不是一个简简单单的 new 操作。当我们 new 一个 线程的时候:

  1. public Thread() {
  2. init(null, null, "Thread-" + nextThreadNum(), 0);
  3. }

然后:

  1. private void init(ThreadGroup g, Runnable target, String name,
  2. long stackSize) {
  3. init(g, target, name, stackSize, null);
  4. }

然后:

  1. private void init(ThreadGroup g, Runnable target, String name,
  2. long stackSize, AccessControlContext acc) {
  3. ......
  4. if (parent.inheritableThreadLocals != null)
  5. this.inheritableThreadLocals =
  6. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  7. /* Stash the specified stack size in case the VM cares */
  8. this.stackSize = stackSize;
  9. ......
  10. }

这时候有一句 'ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);' ,然后

  1. static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
  2. return new ThreadLocalMap(parentMap);
  3. }

继续跟踪:

  1. private ThreadLocalMap(ThreadLocalMap parentMap) {
  2. Entry[] parentTable = parentMap.table;
  3. int len = parentTable.length;
  4. setThreshold(len);
  5. table = new Entry[len];
  6. for (int j = 0; j < len; j++) {
  7. Entry e = parentTable[j];
  8. if (e != null) {
  9. @SuppressWarnings("unchecked")
  10. ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
  11. if (key != null) {
  12. Object value = key.childValue(e.value);
  13. Entry c = new Entry(key, value);
  14. int h = key.threadLocalHashCode & (len - 1);
  15. while (table[h] != null)
  16. h = nextIndex(h, len);
  17. table[h] = c;
  18. size++;
  19. }
  20. }
  21. }
  22. }

当我们创建一个新的线程的时候X,X线程就会有 ThreadLocalMap 类型的 inheritableThreadLocals ,因为它是 Thread 类的一个属性。

然后

先得到当前线程存储的这些值,例如 Entry[] parentTable = parentMap.table; 。再通过一个 for 循环,不断的把当前线程的这些值复制到我们新创建的线程X 的inheritableThreadLocals 中。就这样,就ok了。

那么这样会有一个什么结果呢?

结果就是我们创建的新线程X 的inheritableThreadLocals 变量中已经有了值了。那么我在新的线程X中调用threadlocal.get() 方法,首先会得到新线程X 的 inheritableThreadLocals,然后,再根据threadlocal.get()中的 threadlocal,就能够得到这个值。

_这样就避免了 新线程中得到的 threadlocals 没有东西。之前就是因为没有东西,所以才拿不到值。_

所以说 整个 InheritableThreadLocal 的实现原理就是这样的。

总结

  1. 首先要理解 为什么 在 新线程中得不到值,是因为**我们其实是根据 Thread.currentThread(),拿到该线程的 threadlocals,从而进一步得到我们之前预先 set 好的值。那么如果我们新开一个线程,这个时候,由于 Thread.currentThread() 已经变了,从而导致获得的 threadlocals 不一样,我们之前并没有在这个新的线程的 threadlocals 中放入值,那么我就再通过 threadlocal.get()方法 是不可能拿到值的。**
  2. 那么解决办法就是 我们在新线程中,要把父线程的 threadlocals 的值 给复制到 新线程中的 threadlocals 中来。这样,我们在新线程中得到的 threadlocals 才会有东西,再通过 threadlocal.get() 中的 threadlocal,就会得到值。

InheritableThreadLocal原理的更多相关文章

  1. ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑

    虽然使用AOP可以获取方法签名,但是如果要获取方法中计算得出的数据,那么就得使用ThreadLocal,如果还涉及父线程,那么可以选择InheritableThreadLocal. 注意:理解一些原理 ...

  2. ThreadLocal及InheritableThreadLocal的原理剖析

    我们知道,线程的不安全问题,主要是由于多线程并发读取一个变量而引起的,那么有没有一种办法可以让一个变量是线程独有的呢,这样不就可以解决线程安全问题了么.其实JDK已经为我们提供了ThreadLocal ...

  3. 关于Spring事务的原理,以及在事务内开启线程,连接池耗尽问题.

    主要以结果为导向解释Spring 事务原理,连接池的消耗,以及事务内开启事务线程要注意的问题. Spring 事务原理这里不多说,网上一搜一大堆,也就是基于AOP配合ThreadLocal实现. 这里 ...

  4. TransmittableThreadLocal 解决 线程池线程复用 无法复制 InheritableThreadLocal 的问题.

    ThreadLoacl,InheritableThreadLocal,原理,以及配合线程池使用的一些坑 TransmittableThreadLocal 原理 之前为了能让InheritableThr ...

  5. 【并发编程】ThreadLocal的兄弟InheritableThreadLocal

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 引子 public class InheritableT ...

  6. ThreadLocal源码解析

    主要用途 1)设计线程安全的类 2)存储无需共享的线程信息 设计思路 ThreadLocalMap原理 1)对象存储位置-->当前线程的ThreadLocalMap ThreadLocalMap ...

  7. ThreadLocal源码解读

    1. 背景 ThreadLocal源码解读,网上面早已经泛滥了,大多比较浅,甚至有的连基本原理都说的很有问题,包括百度搜索出来的第一篇高访问量博文,说ThreadLocal内部有个map,键为线程对象 ...

  8. InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)

      上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢?   测试代码 p ...

  9. ThreadLocal系列(二)-InheritableThreadLocal的使用及原理解析

    ThreadLocal系列之InheritableThreadLocal的使用及原理解析(源码基于java8) 上一篇:ThreadLocal系列(一)-ThreadLocal的使用及原理解析 下一篇 ...

随机推荐

  1. Lintcode: Maximum Subarray III

    Given an array of integers and a number k, find k non-overlapping subarrays which have the largest s ...

  2. 【Origin】 偶题 之 抒意

    作词抒胸臆, 辞赋九万里: 从南盖到北, 句句表挚期. -作于二零一五年七月十五日

  3. HDU 3308 LCIS(线段树)

    Problem Description Given n integers.You have two operations:U A B: replace the Ath number by B. (in ...

  4. bzoj4547 小奇的集合

    当序列中最大和次大都是负数的时候,其相加会是一个更小的负数,因此答案为(Σai)+(m1+m2)*k,如果最大是正数次大是负数,那么一直相加直到两个数都为正数,当最大和次大都是正数时,做一下矩阵乘法即 ...

  5. bzoj3489 A simple rmq problem 可持久化树套树

    先预处理出两个个数组pre,next.pre[i]表示上一个与i位置数字相同的位置,若不存在则设为0:next[i]表示下一个与i位置数字相同的位置,若不存在则设为n+1.那么一个满足在区间[L,R] ...

  6. 。。。Hibernate注解配置的注意事项。。。

    今天本来打算录视频的,突然遇到一个拦路虎,Hibernate注解配置,有一个注意点:要么都在属性上面注解配置,要么都在getXX()方法上面用注解配置,要不然就会报错: Caused by: org. ...

  7. linux下的网络配置

    临时生效的命令: 设置静态ip: ip addr add 192.168.1.2/24 dev eth0 设置网关: ip route add default via 192.168.1.1 启动某个 ...

  8. 《Focus On 3D Terrain Programming》中一段代码的注释二

    取自<Focus On 3D Terrain Programming>中的一段: bool CTERRAIN::MakeTerrainFault( int iSize, int iIter ...

  9. Sql server analysis service 通过IIS连接时的最大连接数问题

    做过SSAS项目的大部分人都应该知道SSAS是可以通过在IIS上建立代理站点的方式来建立远程连接的,这样可以绕过连接到SSAS时需要在同一个域环境下通过域用户来验证的问题,这样即使连接到SSAS的客户 ...

  10. java设置环境变量小工具

    unit MainUnit; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Fo ...