详解一个ThreadLocal 的谜题
多线程如果不理解透彻, 那么 ThreadLocal 始终是有些会有所迷糊的。
ThreadLocal 本身的命名有有问题, 这些美国精英整出来的技术,再加上一个奇怪的命名。对我们中国人来说,就是一场场的灾难。
如下的问题, 你觉得输出是多少呢?
/** * Created by lk on 2017/6/8. */ public class ThreadLocalTest extends Thread { ThreadLocal<Long> threadLocal = new ThreadLocal<Long>() { @Override protected Long initialValue() { return 2L; } }; public ThreadLocalTest() { setThreadLocal(new ThreadLocal<Long>()); } @Override public void run() { show(); } public void setThreadLocal(ThreadLocal<Long> threadLocal) { this.threadLocal = threadLocal; threadLocal.set(10L); } public void show() { System.out.println( threadLocal.get()); } public static void main(String[] args) { ThreadLocalTest ThreadLocalTest = new ThreadLocalTest(); ThreadLocalTest.run(); ThreadLocalTest.start(); } }
答案是
10
null
这里有几个陷阱。
首先,ThreadLocalTest.run(); 这行由于要启动一个新的线程 以及它要初始化一个map(ThreadLocal 内部的东西)等, 它执行show 方法的时间通常会ThreadLocalTest.start(); 所以其实是 ThreadLocalTest.run(); 打印了null, 而 ThreadLocalTest.start(); 输出了 10。 就是说 后者比前者先输出。我们 把show 方法改成下面的样子:
System.out.println(" local = " + Thread.currentThread().getName() + " " + threadLocal.get());
就会得到:
local = main 10
local = Thread-0 null
其次,ThreadLocalTest.run(); 和 ThreadLocalTest.start(); 方法看起来是执行了一样的代码, 其实不然, 这就是ThreadLocal 的非常的令人迷惑的地方。 要理解ThreadLocal , 关键在于理解其 get / set 方法, ThreadLocal 和 Thread 的关系, ThreadLocal 和 ThreadLocalMap 的关系 。一定要理清。 我们可以知道。 这里有两个线程, 一个是 main, 一个是 ThreadLocalTest。
main 线程的执行路径是: new 一个 ThreadLocalTest ,new 的过程中, 修改了当前类变量 threadLocal(其初始值是2L),让其指向了一个新的 ThreadLocal , 其没有初始值,也就是 null (查看源码可知)。 然后又将 其值设置为 10L。 这里一定要搞清楚, 最开始的那个 threadLocal 变量的堆实例已经无法获取了, 已经变成垃圾了, 随时可能被GC了, 现在的threadLocal 变量仅仅是一个 引用, 其指向的实例 已经不是原来那个了, 初始值已经变成了 null了。threadLocal.set(10L); 改成 this.threadLocal.set(10L); 是没有任何影响的, 他们是同一个实例。
至此, 我们应该明白了, main线程对应的 ThreadLocalMap 实例存放的 threadLocal 对应的 key 的 值 是 10L, 故 main 的start 方法调用的 show , get 到的 threadLocal 的值 就是 10。
ThreadLocalTest 线程的执行路径 是明显不同的, ThreadLocalTest 只管启动一个线程, 然后执行 run 方法,run 方法调用的 show , get 到的 threadLocal 的值 是多少呢? 我们先看一下 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(); }
ThreadLocalTest 线程执行到 ThreadLocalMap map = getMap(t); 获取的 map 一定是 null, 因为ThreadLocalTest 线程 是刚刚创建的,其 对应的 ThreadLocalMap 一定是 null ( 查看 Thread 的源码可知), 这样, ThreadLocalMap 立即执行 return setInitialValue(); 尝试设置 初始值,然后返回初始值。 setInitialValue 方法 其实主要是获取了 initialValue 方法的值, 然后设置到了 ThreadLocalMap 中去。 前面的分析我们得知,现在的 threadLocal 已经被悄悄改变了, 它没有复写initialValue 方法, 其初始值就是默认值, 就是null。 故 get 的结果 就是 null 。 至此, 分析完毕。
欢迎热爱底层技术的人和我一起遨游技术的海洋。还有不懂的请留言。
相关文章
- make menuconfig makefile kconfig详解
- MySQL之MyISAM存储引擎的非聚簇索引详解
- 退出Vim编辑器详解程序员
- Wince6.0 无线终端扫描设置加一个回车详解程序员
- 判断一个字符串是否被Base64加密详解程序员
- linux命令将一个文件夹里面的所有文件复制到指定文件里详解程序员
- Linux下Mysql安装详解程序员
- 查询一个PostgreSQL表详解数据库
- ios后台更新和下载详解手机开发
- 微信企业号开发:获取数据权限错误如何处理详解手机开发
- adb 打印日志到本地详解手机开发
- Android 写一个属于自己的Rxjava(一)详解手机开发
- win10 编译 Android ffmpeg详解手机开发
- Linux负载均衡软件LVS简介详解架构师
- python编程从一个ftp传输文件到另一个ftp服务器详解编程语言
- 用pytho写的分割pdb文件一个脚本详解编程语言
- jquery 跑马灯抽奖详解编程语言
- 一个简单的抽奖转盘游戏详解编程语言
- Drools 规则引擎—-向领域驱动进步(六)详解编程语言
- 一个很easy的脚本–php获取服务器端的相关信息详解编程语言
- jQuery File Upload v9.12.6 发布,一个非常优秀的上传组件详解编程语言
- bootstrap-datepicker v1.6.2发布,一个日期表单组件详解编程语言
- Vue中的scoped及穿透方法详解编程语言
- JS判断一个字符串是否包含一个子串详解编程语言
- 使用JBPM4实现一个简单的工作流例子详解编程语言
- 一个完整的Lucene搜索引擎例子demo详解编程语言
- 在Openfire上弄一个简单的推送系统详解编程语言
- 基于SpringBoot开发一个Restful服务,实现增删改查功能详解编程语言
- apache.commons.compress 压缩,解压详解编程语言
- Java的整个字符串的结束索引在最后一个字符之外详解编程语言
- 一个短小的JS函数,用来得到仅仅包含不重复元素的数组详解编程语言
- ABAP-增强-层级BOM-AB件业务详解编程语言
- spring boot 请求地址带有.json 兼容处理详解编程语言
- java实现的一个【快速排序 】算法【原创】详解编程语言
- C++继承(详解版)
- MySQL事务隔离级别详解(附带实例)
- 创建MySQL数据表的步骤详解(mysql怎么创建一个表)