ThreadLocal 原理分析
用法
ThreadLocal<String> threadLocal = new ThreadLocal<>(); // 无初始值
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "123"); // 有初始值
threadLocal.set("123"); // set操作
threadLocal.get(); // get操作
threadLocal.remove(); // remove操作
一个小例子:
public static void main(String[] args) throws InterruptedException {
ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("Hello");
System.out.println("现在线程是" + Thread.currentThread().getName() + ", 尝试获取:" + threadLocal.get());
new Thread(() -> {
threadLocal.set("World");
System.out.println("现在线程是" + Thread.currentThread().getName() + ", 尝试获取:" + threadLocal.get());
threadLocal.remove();
}).start();
Thread.sleep(3000);
System.out.println("现在线程是" + Thread.currentThread().getName() + ", 尝试获取:" + threadLocal.get());
threadLocal.remove();
}
输出:
现在线程是main, 尝试获取:Hello
现在线程是Thread-0, 尝试获取:World
现在线程是main, 尝试获取:Hello
实现
set操作
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value); // 创建map
}
// getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
// createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY]; // INITIAL_CAPACITY = 16;
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 计算下标
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY); // 设置阈值
}
// 哈希值
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT); // HASH_INCREMENT = 0x61c88647;
}
// ThreadLocalMap数据结构
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}

private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); // 下标
// 使用线性探测法来解决哈希冲突
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // 获取弱引用ThreadLocal
if (k == key) { // 对于已经存在的key,直接赋值
e.value = value;
return;
}
if (k == null) { // 弱引用ThreadLocal为null,进行替换
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 上面两种情况不是,直接赋值
int sz = ++size; // size+1
if (!cleanSomeSlots(i, sz) && sz >= threshold) // 清除无效entry,大于阈值,扩容并重新散列化
rehash();
}
get操作
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 传入自己,也就是threadlocal对象,得到entry
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1); // 获得下标
Entry e = table[i]; // 取值
if (e != null && e.get() == key) // 正好取到,直接返回
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) { // 依次加一往后取值
ThreadLocal<?> k = e.get();
if (k == key) // 取到返回
return e;
if (k == null) // 弱引用ThreadLocal为null,清除无效的entry
expungeStaleEntry(i); //
else
i = nextIndex(i, len); // 下一个
e = tab[i];
}
return null;
}
remove操作
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear(); // 弱引用的引用置为null
expungeStaleEntry(i); // 清除entry,并重新散列化
return;
}
}
}
public void clear() {
this.referent = null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 槽置为null
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 后面的进行重新散列化
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
内存泄露

当ThreadLocal没有强依赖,ThreadLocal会在下一次发生GC时被回收,key是被回收了,但是value却没有被回收,为了防止这个问题出现,最好手动调用remove方法。
ThreadLocal 原理分析的更多相关文章
- java基础解析系列(七)---ThreadLocal原理分析
java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...
- ThreadLocal原理分析
本文结构 ThreadLocal简介 (简要说明ThreadLocal的作用) ThreadLocal实现原理(说明ThreadLocal的常用方法和原理) ThreadLocalMap的实现 (说明 ...
- ThreadLocal原理分析与使用场景
什么是ThreadLocal变量 ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本.这里有几点需要注意: 因为每个 Thr ...
- ThreadLocal原理分析与代码验证
ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...
- ThreadLocal原理及其实际应用
前言 java猿在面试中,经常会被问到1个问题: java实现同步有哪几种方式? 大家一般都会回答使用synchronized, 那么还有其他方式吗? 答案是肯定的, 另外一种方式也就是本文要说的Th ...
- Handler系列之原理分析
上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...
- [转]Handler MessageQueue Looper消息循环原理分析
Handler MessageQueue Looper消息循环原理分析 Handler概述 Handler在Android开发中非常重要,最常见的使用场景就是在子线程需要更新UI,用Handler ...
- Junit 注解 类加载器 .动态代理 jdbc 连接池 DButils 事务 Arraylist Linklist hashset 异常 哈希表的数据结构,存储过程 Map Object String Stringbufere File类 文件过滤器_原理分析 flush方法和close方法 序列号冲突问题
Junit 注解 3).其它注意事项: 1).@Test运行的方法,不能有形参: 2).@Test运行的方法,不能有返回值: 3).@Test运行的方法,不能是静态方法: 4).在一个类中,可以同时定 ...
- Eventbus 使用方法和原理分析
对于 Eventbus ,相信很多 Android 小伙伴都用到过. 1.创建事件实体类 所谓的事件实体类,就是传递的事件,一个组件向另一个组件发送的信息可以储存在一个类中,该类就是一个事件,会被 E ...
随机推荐
- Web服务器-服务器开发-返回浏览器需要的页面 (3.3.2)
@ 目录 1.说明 2.代码 关于作者 1.说明 使用正则表达式,匹配客户端的请求头 获取到请求的路径 返回对应请求路径的文字 可以使用打开对应文件的方式去返回对应的文件 2.代码 from sock ...
- C++libcurl的使用
一.libcurl描述: 在curl的官方网站 **http://curl.haxx.se/download.html** 提供编译好libcurl包, 最后写一个demod工程,演示下libcur ...
- Docker 部署 _实现每日情话 定时推送(apscheduler)
由于最近工作比较忙,后续博客可能更新不及时,哈哈 前言: 由于python对于微信推送不够友好,需要扫码登录,短信接口需要RMB.我就想到了qq邮箱发送到好友,然而微信有qq邮箱提醒功能,就实现了我需 ...
- Java JVM——5.Java虚拟机栈
虚拟机栈概述 由于跨平台性的设计,Java 的指令都是根据栈来设计的.不同平台CPU架构不同,所以不能设计为基于寄存器的. 栈实现的优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功 ...
- (四)linux的常用环境变量及设置
一.为什么要设置环境变量 1.环境变量能解决什么问题? 你是否经历过输入$python命令后,屏幕上打印出python:command not found的尴尬:每一次都要输入$/home/tools ...
- 如何写出安全的、基本功能完善的Bash脚本
每个人或多或少总会碰到要使用并且自己完成编写一个最基础的Bash脚本的情况.真实情况是,没有人会说"哇哦,我喜欢写这些脚本".所以这也是为什么很少有人在写的时候专注在这些脚本上. ...
- 加班申请单flowable中
/* * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. * * Redistribution and use in source ...
- Java编译执行过程
在刷软件设计师中级考试的题目,判断关于编译系统对某高级语言进行翻译的叙述的对错.记得刚开始学Java的时候自己就觉得自己对程序的执行过程理解的相当的透彻,但是一对答案,我的小心脏就有点受不了了,特此在 ...
- linux下eclipse
最近想学习C++,所以就重新安装了linux系统,虽然这两者没有什么联系,但是我还是比较喜欢linux系统,所以在linux下安装了Eclipse,想起了当初学习Red Hat 9.0时的痛苦场景,哎 ...
- 10年前,我就用 SQL注入漏洞黑了学校网站
我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...