ThreadLocal详解
一、简介
ThreadLocal 是JDK提供的一个操作线程本地变量的工具,填充的数据隶属于当前操作线程栈,变量数据相对于其他线程是不可见的,起到数据隔离的作用,规避线程安全问题。
一个简单的代码示例如下:
public class ThreadLocalTest {
public static void main(String[] args) throws Exception{
ThreadLocal threadLocal = new ThreadLocal();
Thread.currentThread().setName("main-test");
threadLocal.set("123");
System.out.println("one:" + threadLocal.get());
}
}
二、原理
1、结构
Thread 中定义两个类型均为 ThreadLocalMap 的变量 threadLocals(独占) 和 inherittableThreadLocals(可共享),主要用于存储线程本地变量。ThreadLocalMap 是 ThreadLocal 的静态内部类,类似于Map的 KV 存储结构。ThreadLocal 可以理解为操作线程本地变量的工具,通过 set 方法将 value 添加到 threadLocals 中去,通过 get 方法可以去获取当前线程变量中的 threadLocals 值。如果需要移除值,则调用 remove 方法。
2、源码
2.1、set
public void set(T value) { Thread t = Thread.currentThread(); //获取当前线程对象 ThreadLocalMap map = getMap(t); //获取当前线程的threadLocals if (map != null) map.set(this, value); //如果 threadLocals 非空,添加 threadLocals 值,key为当前 ThreadLocal实例对象,value为传入的待设值 else createMap(t, value); //如果 threadLocals 为空, 则进行创建初始化操作 }
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); //创建初始化,key为当前 ThreadLocal 实例对象 }
2.2、get
public T get() { Thread t = Thread.currentThread(); //获取当前线程对象 ThreadLocalMap map = getMap(t); //获取threadLocals if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); // threadLocals非空,以当前 ThreadLocal的实例对象为key在查找map中 if (e != null) { //有值获取返回 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); //没有获取到值,则进行初始化操作 } private T setInitialValue() { T value = initialValue(); //初始 value Thread t = Thread.currentThread(); //当前操作线程 ThreadLocalMap map = getMap(t); //获取threadLocals if (map != null) map.set(this, value); //不为null,直接设值 else createMap(t, value); //为null,创建初始化 return value; //返回初始化创建的 value,即为null } protected T initialValue() { return null; }
2.3、remove
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); //根据当前线程对象,获取 threadLocals if (m != null) m.remove(this); //不为空,以当前 ThreadLocal 实例对象为 key 去删除 threadLocals 中相应的值 }
2.4、ThreadLocal.ThreadLocalMap
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
(1)ThreadLocalMap 类似于Map的 KV存储,只有基本的数组结构,并没有链表或者红黑树去解决hash冲突问题,发生冲突的时候,如果同一个 ThreadLocal,则进行覆盖作业。如果不同的话,则顺次的往后查找可存储的空间。
(2)key设计为弱引用结构,在 ThreadLocal 没有外部强引用的时候,只要发生GC回收,则会被资源回收掉,避免一定的内存泄漏。Java的对象引用可查看 https://www.cnblogs.com/eric-fang/p/10310399.html
3、问题
ThreadLocal 在保存的时候将自己当做 key 存在 ThreadLocalMap 中,并被设计成WeakReference弱引用,当 ThreadLocal 没有外部的强引用的时候,会被GC资源回收,这时候创建线程依旧存活运行的话,Entry 中 value就一直不可回收,产生内存泄漏问题。比如在使用线程池的时候,因为线程的回收再利用,之前设置的 value 就可能一直得不到释放回收,一直占据内存资源。
下面上实验对象:
(1)程序断点
(2)堆栈
(3) 解决
线程使用完毕,清理 threadLocals,调用ThreadLocal.remove()
4、共享 inheritTableThreadLocals
上面说明的是线程独占的threadLocals,使用看起来简单明了。JDK也提供了父子线程共享的 inheritTableThreadLocals,用于在线程间共享资源。
4.1、示例
public class ThreadLocalTest3 { public static void main(String[] args) { Thread.currentThread().setName("main-test"); ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("123"); new Thread(() -> { System.out.println(threadLocal.get()); }).start(); } }
4.2、原理
截取 Thread 初始化最关键部分的代码,通过new Thread(Runnable target) 创建对象的时候,init 最终会执行到上图中的代码中位置,标志位 inheritThreadLocals 为 true,parent 为父线程,当父线程的inheritableThreadLocals变量非空的时候,将父线程的 inheritableThreadLocals 塞给当前线程的inheritableThreadLocals变量,完成线程间的数据传递。
.
原文地址:https://www.cnblogs.com/eric-fang/p/13680015.html
- 温故而知新:设计模式之组合模式(Composite)
- ruby学习笔记(7)-闭包
- ruby学习笔记(6)-Array的使用
- centos7下部署iptables环境纪录(关闭默认的firewalle)
- ruby学习笔记(5)-模块module的运用
- linux系统root密码遗忘的情况下的解决办法
- ruby学习笔记(4)-动态修改类的属性
- 如果技术是一种生命
- ruby学习笔记(2)--类的基本使用
- 域名资讯:四声域名BHHS.com被BHHS公司收购
- ruby学习笔记(1)--初识语法
- 无法启用数据库中的 Service Broker,因为已存在启用的具有相同 ID 的 Service Broker。
- Centos7.2下针对LDAP的完整部署记录
- .NET Core 已经实现了PHP JIT,现在PHP是.NET上的一门开发语言
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 实现 Base64 的编码解码
- Android实现底部导航栏的主界面
- Spring注解@Autowired源码分析
- 使用RecyclerView实现水平列表
- android实现记住用户名和密码以及自动登录
- Android碎片fragment实现静态加载的实例代码
- android异步消息机制 源码层面彻底解析(1)
- /undefinedcss/modules/layer/default/layer.css?v=3.1.1找不到的问题
- Android实现渐变启动页和带有指示器的引导页
- the input device is not a TTY. If you are using mintty, try prefixing the comma
- 【STM32F407】第13章 RL-TCPnet V7.X之创建多个TCP客户端
- RecyclerView实现列表倒计时
- wsl设置默认账户为root(ubuntu18.04)
- LeetCode No.14 最长公共前缀
- android异步消息机制 从源码层面解析(2)