ThreadLocal解析:父线程的本地变量不能传递到子线程详解
众所周知,ThreadLocal类是java提供线程本地变量的工具类。但父线程的本地变量却不能被子线程使用,代码如下:
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("abc");
System.out.println("父线程:"+threadLocal.get());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.print("子线程:"+threadLocal.get());
}
});
t1.start();
}
运行结果如下:

至于原因呢,得先了解ThreadLocal存储的变量是怎么存储的。首先,让我们先看看Thread类的源码:在thread类中有声明这么一个成员变量——threadLocals
ThreadLocal.ThreadLocalMap threadLocals = null;
根据定义可以看出,这是ThreadLocal类里的静态内部类,它的结构是Map结构,以键值对的形式存储值。key值就是当前ThreadLocal类的实例,value值就是当前线程的本地变量。所以线程的本地变量是存在线程实例当中的,而不是存在ThreadLocal中。ThreadLocal只是一个工具类,体现在当ThreadLocal实例调用set()方法时,会将当前线程的threadLocals变量实例化。以下是ThreadLocal类的set()方法源码。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,先是获取当前线程的实例,再获取线程的成员变量threadLocals,如果threadLocals已经实例化,就直接以当前ThreadLocal类为key,存储的本地变量为value,存进threadLocals中。所以线程本地变量是存储在线程中的。如果threadLocals未实例化,则调用createMap()方法,该方法会调用ThreadLocalMap的构造方法,初始化一个以当前ThreadLocal类为key,存储的本地变量为value的Map。该Map是类似HashMap的,感兴趣的话可以继续往下看源码,此处不做扩展。
回到最开始的问题,子线程调用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();
}
此时,会产生两个分支,一个是通过子线程的ThreadLocalMap的getEntry()方法获取Entry并返回value值,但由于子线程的ThreadLocalMap!=父线程的ThreadLocalMap,所以获取不到父线程的本地变量。另一个分支是返回setInitialValue()的返回值,setInitialValue()方法源码如下:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
可以看到value会默认为null,如果子线程的ThreadLocalMap已经实例化,则直接以ThreadLocal实例为key,null为value存进ThreadLocalMap中,否则就创建一个同上所述的ThreadLocalMap。
引发出的问题:ThreadLocal类是弱引用,一次GC后会为null,当key为null时,value值却还存在内存中,造成内存泄漏。所以ThreadLocal类最后一定要执行remove()方法。在GC之前将内存释放。
综上,第一次写博客,写得不好或者有错误的地方,请指正。
ThreadLocal解析:父线程的本地变量不能传递到子线程详解的更多相关文章
- 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解
Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...
- Linux线程体传递参数的方法详解
传递参数的两种方法 线程函数只有一个参数的情况:直接定义一个变量通过应用传给线程函数. 例子 #include #include using namespace std; pthread_t thre ...
- python theading线程开发与加锁、信号量、事件等详解
线程有2种调用方式,如下: 直接调用 import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on ...
- java打开本地应用程序(调用cmd)---Runtime用法详解
有时候我们需要借助java程序打开电脑自带的一些程序,可以直接打开或者借助cmd命令窗口打开一些常用的应用程序或者脚本,在cmd窗口执行的命令都可以通过这种方式运行. 例如: package cn.x ...
- Python3关于current_app传递给子线程
在学习Flask的时候,<Flask Web开发>这本书中有一个异步发送email的例子,其中用到了线程 from . import mail,create_app def send_as ...
- Java线程sleep,yield,join,wait方法详解
1.sleep() 当一个线程调用sleep方法后,他就会放弃cpu,转到阻塞队列,sleep(long millis)方法是Thread类中的静态方法,millis参数设定线程睡眠的时间,毫秒为单位 ...
- vue 父组件中的数据如何传递给子组件
父组件:<template> <div id="app"> <img src="./assets/logo.png"> &l ...
- 【Java 线程的深入研究4】ThreadPoolExecutor运转机制详解
hreadPoolExecutor机制 一.概述 1.ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调 ...
- Android笔记(三十一)Android中线程之间的通信(三)子线程给主线程发送消息
先看简单示例:点击按钮,2s之后,TextView改变内容. package cn.lixyz.handlertest; import android.app.Activity; import and ...
随机推荐
- 禁用gpu首选
import osos.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"os.environ["CUDA_VIS ...
- 用一维数组实现栈(C++编程思想 p120)
1 实现思路 向栈中插入4个元素后的状态 执行过程分析: 2 代码实现 clib.h 接口定义 typedef struct CStashTag { int ele_size; //栈中每个元素的占用 ...
- Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.1.3.RELEASE:repackage (repackage)
解决方案是删除 pom.xml配置的问题 <build> <plugins> <plugin> <groupId>org.springframework ...
- 走过的laravel-admin 的坑
一.http://laravel-admin.org/docs/#/zh/ 大家可以根据这个安装1.5 版本的laravel后台管理, 他很方便哦,有很多方法他都自己自己封装了. 二.大家如果想好好 ...
- python基础五之字典
python数据的可变性 通过数据的可变性,可将数据分为可变数据类型和不可变数据类型. 可变数据类型:list,dict (不可哈希) 不可变数据类型:元祖,bool,int,str (可哈希) py ...
- Python--day37--守护进程和几个常用的方法
1,p.daemon = True #设置子进程为守护进程 #守护进程会随着主进程的代码执行完毕 而结束 #子进程 --> 守护进程 import time from multiprocessi ...
- Python--day34--socket模块的方法
官方文档对socket模块下的socket.send()和socket.sendall()的解释如下: sk.setblocking(False)方法 import socket sk = socke ...
- js三大框架出现的意义
解决了原始html,css,js的UI与数据状态之间同步的难题,避免了大量的操作DOM代码. 使用了React,Angular和Vue,我们只需要定义一次 UI 界面,不再需要为每个操作编写特定的 U ...
- Linux 内核硬件资源
一个 ISA 设备可配备有 I/O 端口, 内存区, 和中断线. 尽管 x86 处理器支持 64 KB I/O 端口内存(即, 处理器有 16 条地址线), 一些老 PC 硬 件仅解码最低的 10 位 ...
- es6笔记 day2---字符串模板及字符串新增
字符串连接案例 注意:引号变了,为键盘数字1旁边的飘花键 以前的老写法是在字符串中加入“+”号,给几个字符串给串起来,那种写法是要死人的. 现在只需加上一对``即可将字符串连接起来 --------- ...