Java 多线程:InheritableThreadLocal 实现原理
前言
介绍 InheritableThreadLocal 之前,假设对 ThreadLocal 已经有了一定的理解,比如基本概念,原理,如果没有,可以参考:Java 多线程:threadlocal关键字。
这里再复习下 ThreadLocal 的原理,因为会对 InheritableThreadLocal 的理解 有重大的帮助:
- 每个线程都有一个 ThreadLocalMap 类型的 threadLocals 属性。
- ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。
- 当我们通过
threadLocal.set(new Integer(123));
,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123)); 的 threadlocal,value 就是值。 - 当我们通过
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 的实现原理就是这样的。
总结
- 首先要理解 为什么 在 新线程中得不到值,是因为我们其实是根据 Thread.currentThread(),拿到该线程的 threadlocals,从而进一步得到我们之前预先 set 好的值。那么如果我们新开一个线程,这个时候,由于 Thread.currentThread() 已经变了,从而导致获得的 threadlocals 不一样,我们之前并没有在这个新的线程的 threadlocals 中放入值,那么我就再通过 threadlocal.get()方法 是不可能拿到值的。
- 那么解决办法就是 我们在新线程中,要把父线程的 threadlocals 的值 给复制到 新线程中的 threadlocals 中来。这样,我们在新线程中得到的 threadlocals 才会有东西,再通过
threadlocal.get()
中的 threadlocal,就会得到值。
参考
- java concurrency in practice读书笔记---ThreadLocal原理
- ThreadLocal和synchronized的区别?
Java 多线程:InheritableThreadLocal 实现原理相关推荐
- java多线程与并发原理
三.java多线程与并发原理 1.进程和线程的区别: 进程和线程的由来: (1)串行:初期的计算机只能串行执行任务,并且需要长时间等待用户输入: (2)批处理:预先将用户的指令集集中成清单,批量串行处 ...
- Java多线程与并发-原理
一.synchronized 线程安全问题的主要诱因 1.存在共享数据(也称临界资源) 2.存在多条线程共同操作这些共享数据 解决问题的根本方法: 同一时刻有且只有一个线程在操作共享数据,其他线程必须 ...
- 面试大厂不看这两份Java面试核心知识点原理篇+框架篇,有个屁用?食屎啦泥?
前言 面试在即,Java知识点很凌乱? 别急,有本套书在呢! 除了原理,还有框架! ★ 精细讲解JVM原理.Java基础.并发编程.数据结构和算法.网络与负载均衡 ★ 深入挖掘数据库与分布式事务.分布 ...
- Java多线程系列--【JUC线程池 02】- 线程池原理(一)
参考:http://www.cnblogs.com/skywang12345/p/java_threads_category.html 概要 在前面一章"Java多线程系列--"J ...
- Java程序的运行原理及JVM的启动是多线程的吗?
Java程序的运行原理及JVM的启动是多线程的吗? A:Java程序的运行原理 Java通过java命令会启动java虚拟机.启动JVM,等于启动了一个应用程序,也就是启动了一个进程. 该进程会自动启 ...
- Java多线程闲聊(四):阻塞队列与线程池原理
Java多线程闲聊(四)-阻塞队列与线程池原理 前言 复用永远是人们永恒的主题,这能让我们更好地避免重复制造轮子. 说到多线程,果然还是绕不开线程池,那就来聊聊吧. 人们往往相信,世界是存在一些规律的 ...
- Java多线程之线程池7大参数、底层工作原理、拒绝策略详解
Java多线程之线程池7大参数详解 目录 企业面试题 线程池7大参数源码 线程池7大参数详解 底层工作原理详解 线程池的4种拒绝策略理论简介 面试的坑:线程池实际中使用哪一个? 1. 企业面试题 蚂蚁 ...
- Java 多线程 —— 深入理解 volatile 的原理以及应用
转载自 Java 多线程 -- 深入理解 volatile 的原理以及应用 推荐阅读:<java 多线程-线程怎么来的> 这一篇主要讲解一下volatile的原理以及应用,想必看完这一篇 ...
- Java多线程神器:join使用及原理
转载自 Java多线程神器:join使用及原理 join() join()是线程类 Thread的方法,官方的说明是: Waits for this thread to die. 等待这个线程结束,也 ...
最新文章
- linux mysql 1030,MySQL报错ERROR 1030 (HY000): 解决过程
- 2019年云计算发展状态
- 物理专线与虚拟专线的比较
- sdut 2088 数据结构实验之栈与队列十一:refresh的停车场
- iis下的php不显示报错信息,IIS环境下 PHP无法显示错误信息怎么办
- 通用唯一标识码UUID的介绍及使用
- 使用ajax怎么解决乱码问题,一句话解决AJAX中文乱码问题[推荐]
- 计算机二级34套word答案,全国计算机二级C选择题题库第34套
- mysql unescape解码_python mysql escape\n(简单语法错误)
- 2016.01.10 论文扩充、精修、查重
- 谐波小波 matlab,基于谐波小波的电力系统谐波分析
- 收藏一些web应用,留作DzzOffice日后添加web应用时使用。
- javaWeb连接数据库实现简单的登陆注册功能
- 蒋鑫鸿:9.7国际黄金、纸白银行情走势分析、原油操作建议
- UE4中的Spline用法
- Linux14.04安装Mysql Linux公社
- JAVA实现百度网盘文件上传
- 【图文教程】文件压缩和打包
- 化工厂人员定位系统助力化工企业安全运行
- GD32 开机直接进入了深度睡眠模式导致无法下载
热门文章
- ArcGIS License Manager 更新
- 如何获取不同网站的favicon默认图标
- 购买使用vps建站(3)
- VMware Workstation 8.0安装VMTools时提示找不到GCC的解决办法
- 软件测试——JUnit中的参数化测试
- 【C++ Primer】自己动手编写函数 atoi(char *str)
- 计算机更新要联网吗,解决steam需要在线进行更新。请确认您的网络连接正常的方法...
- 帝国整站PHP源码,帝国cms 诗词整站源码
- 将特定像素点在图像上连接起来_图像分割【论文解读】快速图像分割的SuperBPD方法 CVPR-2020...
- jdbcTemplate 后台接口中的分页