前言

介绍 InheritableThreadLocal 之前,假设对 ThreadLocal 已经有了一定的理解,比如基本概念,原理,如果没有,可以参考:Java 多线程: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()方法 是不可能拿到值的。例如如下代码:

public class Test {public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();public static void main(String args[]){threadLocal.set(new Integer(123));Thread thread = new MyThread();thread.start();System.out.println("main = " + threadLocal.get());}static class MyThread extends Thread{@Overridepublic void run(){System.out.println("MyThread = " + threadLocal.get());}}
}

输出是:

main = 123
MyThread = null

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

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

也就是说,我们把上面的

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

改成

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

再运行,就会有结果:

main = 123
MyThread = 123

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

InheritableThreadLocal 原理

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

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);}
}

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

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

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}

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

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

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

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

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

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

其实,是的:

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

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

public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);
}

然后:

private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null);
}

然后:

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

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

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);
}

继续跟踪:

private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];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 c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}

当我们创建一个新的线程的时候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,就会得到值。

参考

  • java concurrency in practice读书笔记---ThreadLocal原理
  • ThreadLocal和synchronized的区别?

Java 多线程:InheritableThreadLocal 实现原理相关推荐

  1. java多线程与并发原理

    三.java多线程与并发原理 1.进程和线程的区别: 进程和线程的由来: (1)串行:初期的计算机只能串行执行任务,并且需要长时间等待用户输入: (2)批处理:预先将用户的指令集集中成清单,批量串行处 ...

  2. Java多线程与并发-原理

    一.synchronized 线程安全问题的主要诱因 1.存在共享数据(也称临界资源) 2.存在多条线程共同操作这些共享数据 解决问题的根本方法: 同一时刻有且只有一个线程在操作共享数据,其他线程必须 ...

  3. 面试大厂不看这两份Java面试核心知识点原理篇+框架篇,有个屁用?食屎啦泥?

    前言 面试在即,Java知识点很凌乱? 别急,有本套书在呢! 除了原理,还有框架! ★ 精细讲解JVM原理.Java基础.并发编程.数据结构和算法.网络与负载均衡 ★ 深入挖掘数据库与分布式事务.分布 ...

  4. Java多线程系列--【JUC线程池 02】- 线程池原理(一)

    参考:http://www.cnblogs.com/skywang12345/p/java_threads_category.html 概要 在前面一章"Java多线程系列--"J ...

  5. Java程序的运行原理及JVM的启动是多线程的吗?

    Java程序的运行原理及JVM的启动是多线程的吗? A:Java程序的运行原理 Java通过java命令会启动java虚拟机.启动JVM,等于启动了一个应用程序,也就是启动了一个进程. 该进程会自动启 ...

  6. Java多线程闲聊(四):阻塞队列与线程池原理

    Java多线程闲聊(四)-阻塞队列与线程池原理 前言 复用永远是人们永恒的主题,这能让我们更好地避免重复制造轮子. 说到多线程,果然还是绕不开线程池,那就来聊聊吧. 人们往往相信,世界是存在一些规律的 ...

  7. Java多线程之线程池7大参数、底层工作原理、拒绝策略详解

    Java多线程之线程池7大参数详解 目录 企业面试题 线程池7大参数源码 线程池7大参数详解 底层工作原理详解 线程池的4种拒绝策略理论简介 面试的坑:线程池实际中使用哪一个? 1. 企业面试题 蚂蚁 ...

  8. Java 多线程 —— 深入理解 volatile 的原理以及应用

    转载自  Java 多线程 -- 深入理解 volatile 的原理以及应用 推荐阅读:<java 多线程-线程怎么来的> 这一篇主要讲解一下volatile的原理以及应用,想必看完这一篇 ...

  9. Java多线程神器:join使用及原理

    转载自 Java多线程神器:join使用及原理 join() join()是线程类 Thread的方法,官方的说明是: Waits for this thread to die. 等待这个线程结束,也 ...

最新文章

  1. linux mysql 1030,MySQL报错ERROR 1030 (HY000): 解决过程
  2. 2019年云计算发展状态
  3. 物理专线与虚拟专线的比较
  4. sdut 2088 数据结构实验之栈与队列十一:refresh的停车场
  5. iis下的php不显示报错信息,IIS环境下 PHP无法显示错误信息怎么办
  6. 通用唯一标识码UUID的介绍及使用
  7. 使用ajax怎么解决乱码问题,一句话解决AJAX中文乱码问题[推荐]
  8. 计算机二级34套word答案,全国计算机二级C选择题题库第34套
  9. mysql unescape解码_python mysql escape\n(简单语法错误)
  10. 2016.01.10 论文扩充、精修、查重
  11. 谐波小波 matlab,基于谐波小波的电力系统谐波分析
  12. 收藏一些web应用,留作DzzOffice日后添加web应用时使用。
  13. javaWeb连接数据库实现简单的登陆注册功能
  14. 蒋鑫鸿:9.7国际黄金、纸白银行情走势分析、原油操作建议
  15. UE4中的Spline用法
  16. Linux14.04安装Mysql Linux公社
  17. JAVA实现百度网盘文件上传
  18. 【图文教程】文件压缩和打包
  19. 化工厂人员定位系统助力化工企业安全运行
  20. GD32 开机直接进入了深度睡眠模式导致无法下载

热门文章

  1. ArcGIS License Manager 更新
  2. 如何获取不同网站的favicon默认图标
  3. 购买使用vps建站(3)
  4. VMware Workstation 8.0安装VMTools时提示找不到GCC的解决办法
  5. 软件测试——JUnit中的参数化测试
  6. 【C++ Primer】自己动手编写函数 atoi(char *str)
  7. 计算机更新要联网吗,解决steam需要在线进行更新。请确认您的网络连接正常的方法...
  8. 帝国整站PHP源码,帝国cms 诗词整站源码
  9. 将特定像素点在图像上连接起来_图像分割【论文解读】快速图像分割的SuperBPD方法 CVPR-2020...
  10. jdbcTemplate 后台接口中的分页