从源码看ThreadLocal的底层结构

ThreadLocal底层实现原理(满满干货) 现在是下午4点,王二狗已经提前把工作已经干完了,在和女神如花闲聊的过程中逛了逛XXX网站,突然发现部门一位大佬发布的文章,里面讲着多线程、线程变量以及ThreadLocal原理等等,王二狗此时对ThreadLocal非常感兴趣,对女神说:“宝贝儿,你等会,我要去干一件大事!”,接下来王二狗就开始了研究之路。。。如花:“活该你单身!!!” 阅读详情

使用场景

ThreadLocal可以用于多线程情况下的变量保存,各个线程之间的变量值互不影响。关于它的使用场景可以看
刘欣 老师的文章一个故事讲明白线程的私家领地:ThreadLocal,这篇文章通过生动的案列引出了使用场景,令人印象深刻。

使用方法

ThreadLocal<String> threadLocalA= new ThreadLocal<String>();
ThreadLocal<Integer> threadLocalB = new ThreadLocal<Integer>();

//保存
线程1: threadLocalA.set("Sbingo");
        threadLocalB.set(99);
线程2: threadLocalA.set("sbingo");
        threadLocalB.set(100);

//使用
线程1: threadLocalA.get()  --> "Sbingo"
        threadLocalB.get()  --> "99"
线程2: threadLocalA.get() --> "sbingo"
        threadLocalB.get()  --> "100"

可以看到,ThreadLocal的使用方法很简单,为什么这么操作后同一个变量在不同线程中取出的值不一样,并且1个线程可以保存N个ThreadLocal呢?

原理就在Thread类中成员变量threadLocals,它的类型是ThreadLocal.ThreadLocalMap,每个线程都有一个这样的Map,所以可以保存N个ThreadLocal键值对,并且不同线程的变量值不同。

因此,按上面这样操作后相应的数据结构如下:

线程1中map

keyvalue
threadLocalASbingo
threadLocalB99

线程2中map

keyvalue
threadLocalAsbingo
threadLocalB100

变量threadLocals虽然定义在Thread类中,但对它的访问却完全交给了类ThreadLocal,下面我们通过源码看看ThreadLocal.ThreadLocalMap具体是如何保存这些键值对的。

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);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

static class Entry extends WeakReference<ThreadLocal<?>> {
     /** The value associated with this 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();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

private void rehash() {
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

从第11行可以看出,getMap()方法就是返回了刚才提到的Thread类中成员变量threadLocals。

当首次调用set()方法时,会进入第7行的createMap()方法,第15行赋值给提到过的线程变量threadLocals,自此它不再是null。

第15行在构造方法中传入了this,也就是将当前的ThreadLocal本身作为了key。

进入构造方法,table是Map中的哈希表,第19行对其赋值,初始容量为16。table中保存的元素类型为Entry,它是对ThreadLocal的弱引用(第26行),这样便于GC,防止内存泄漏。Entry中只有一个变量value(第28行),value就是Map中每个ThreadLocal对应的值。

第20行将threadLocalHashCode和INITIAL_CAPACITY - 1进行按位与运算,相当于threadLocalHashCode对INITIAL_CAPACITY取模,但速度比用%运算更快,这样就计算出了哈希表中索引的位置。

最后对哈希表相应索引的元素赋值,更新表内元素数量和阙值,完成了首次set。

之后再调用set()方法时,就会进入第5行代码。

还是和之前一样计算出索引,当发生冲突时,进入第41至55行的循环体内。

从第64行可以看出,解决冲突的nextIndex()方法用的是线性探测法。

第46至49行,如果是同一个ThreadLocal对象,则更新对应的值。

第51至54行,如果ThreadLocal的引用为null,则用当前的新值替换旧值,为简化篇幅,具体源码这里不展开了。

如果没发生冲突、发生冲突但遍历结束也没找到相同的ThreadLocal对象或null对象,则插入新值,此时执行第57行。

第59行表明在插入新值后,尝试删除已回收的值,如果没有删除发生并且哈希表内元素数量超过了阙值,则扩容哈希表并重新排列表内元素。

第76行表明阙值大小为哈希表长度的2/3 ,第71行表明扩容时哈希表内元素数量为阙值的3/4,相乘为哈希表大小的1/2。即当哈希表内元素数量超过哈希表大小一半时,就会发生扩容。扩容后的大小为原大小的两倍,这里也不展开分析了。

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();
}

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;
}

protected T initialValue() {
    return null;
}

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)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

当get()发生在set()之前,就会进入第12行代码。此后在第16行调用initialValue()方法返回了null,当然也可以复写该方法以修改默认值。第22行会初始化Map,最后返回了初始值(null或修改后的初始值)。

当然大多数情况下调用get()方法会进入第5行获取Entry对象,在第8、9行获取对应的value值并返回。可见,关键在于getEntry()方法。

第31行还是计算出索引,第33行判断索引处的Entry对象是否为null并比较两个ThreadLocal对象是否为同一个,结果为真的话就可以直接返回Entry对象,否则进入getEntryAfterMiss()方法。

第43至52行的循环体中,不断遍历哈希表,当两个ThreadLocal对象为同一个时,就可以返回哈希表当前索引上的Entry对象。

内存泄漏

ThreadLocal虽然用起来很简单,但使用时要注意内存泄漏问题。

刚才分析时说过,Entry对象是ThreadLocal的弱引用。所以当线程消亡时,垃圾回收器会将它回收,此时不存在内存泄漏问题。

但如果使用线程池,并且线程存在复用的情形,线程不会消亡,这些Entry对象就会发生泄漏,此时就需要我们进行手动清理。

清理的方法很简单,就是调用ThreadLocal的remove()方法,和上面的分析类似,它会不停探测索引,如果找到正确的ThreadLocal对象就将其清除。

相关源码如下:

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();
            expungeStaleEntry(i);
            return;
        }
    }
}
【ThreadLocal】的使用案例和实现原理 ThreadLocal的使用案例和实现原理(1)ThreadLocal简单介绍(1)为什么会有ThreadLocal(2)未使用ThreadLocal时是什么样(3)使用ThreadLocal存放线程设定的值(4)使用synchronized锁也可以实现上述的效果,但是会降低性能(5)ThreadLocal和synchronized的区别(6)ThreadLocal的好处(2)ThreadLocal的基本使用(1)创建ThreadLocal对象(2)设定ThreadLocal的值set()和获取值get() 阅读详情

相关推荐

【ThreadLocal源码】深入浅出ThreadLocal

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候,都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态变量,使用它的目的是希望将状态(用户ID、事务ID等)与线程关联起来。1)、前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,例如当前登录用户放入ThreadLocal 中,避免每个方法一直传递过去,直接从ThreadLocal获取即可;

weixiaohuai的博客 1205

ThreadLocal的底层原理

开发中经常会使用到,使用完不调用remove方法时,可能造成内存泄漏,具体的原因需要懂得其原理才行。

weixin_74261199的博客 1350

pytorch实现手语识别

连续手语识别项目,中文数据集CSL

masteryi-0018的博客 1万+

ThreadLocal底层源码解析

ThreadLocal底层源码解析

weixin_44149903的博客 897

告别重复劳动:用Python+pywinauto打造你的微信个人助理(自动回复/收款/定时发消息)

本文介绍如何利用Python和pywinauto库构建微信个人助手,实现自动回复、定时消息、智能收款等功能,告别重复劳动。通过详细的代码示例和配置指南,帮助开发者快速掌握微信自动化技术,提升工作效率。

weixin_42568801的博客 332

爬取信用中国里面的行政处罚

result字段名为行政处罚文书号、处罚决定日期、处罚内容、处罚金额、违法事实、处罚机关、公司名称,行政处罚次数。file_path=r'E:\Data analyze\python\行政处罚爬虫/'#路径需要按实际修改。writer = pd.ExcelWriter('统计.xlsx')# 写入Excel文件。src_workbook=file_path+r'行政处罚(名单).xls' #表格名称也要更改。# keyword = input("输入爬取的公司全称必须是全称")

qq_42863025的博客 1674

ThreadLocal底层原理及数据结构详解

允许为创建,使得同一个对象在不同的线程中拥有不同的值。它的主要作用是在并发环境下提供,避免多个线程共享同一个变量,从而减少线程间的相互干扰。的核心在于为每个线程维护一个独立的数据副本,它的实现主要依赖于,这是专用的Map,用来存储线程自己的变量。

weixin_43287459的博客 749

别再自己写组件了!用uni-app的midButton属性5分钟搞定中间凸起TabBar(H5/小程序通用)

本文详细介绍了uni-app官方提供的midButton属性,帮助开发者5分钟内实现中间凸起的TabBar效果,无需编写繁琐的自定义组件代码。该方案兼容H5和微信小程序平台,配置简单且维护成本低,大幅提升开发效率和性能表现。

weixin_30431445的博客 348

ThreadLocal详解

ThreadLocal

okfineyell的博客 1638

手把手移植st7701s驱动:MT6763+安卓8.0屏幕适配保姆级教程

本文详细介绍了在MT6763平台上移植ST7701S驱动的完整流程,涵盖从LK层到Kernel层的适配方案,特别针对安卓8.0系统的特殊要求。通过MIPI转RGB屏幕适配的实战教程,帮助开发者解决常见显示问题,包括无显示、色彩异常和屏幕错位等,并提供高级调试技巧如彩条测试模式和闪屏解决方案。

weixin_29186249的博客 157

普源精电DHO系列示波器选购指南:从学生党到工程师的完整对比

本文深入评测普源精电DHO系列示波器,从DHO800到DHO4000各型号的核心参数、功能特点及适用场景进行全面对比,帮助学生党和工程师根据实际需求选择最佳型号。重点分析带宽、采样率、存储深度等关键指标,并提供实用的选购建议和进阶使用技巧,帮助用户充分发挥示波器性能。

weixin_30485379的博客 167

Thread、ThreadLocal、ThreadLocalMap

Thread、ThreadLocal、ThreadLocalMap

qq_44300280的博客 2914

【项目立项管理、项目整合管理】30天通过软考高项-第二天

①项目或阶段的概述;②范围目标、范围的评估标准,证明达到完⼯标准的证据;③质量目标、项目和产品质量的评估标准、相关核实信息和实际里程碑交付日期以及偏差原因;④成本目标,包括可接受的成本区间、实际成本,产⽣任何偏差的原因等;⑤最终产品、服务或成果的确认信息的总结;⑥进度计划目标,包括成果是否实现项目预期效益:如果在项目结束时未能实现效益,则指出效益实现程度并预计未来实现情况;

weixin_42520723的博客 1002

ThreadLocal使用、结构及其源码分析、ThreadLocalMap

ThreadLocal使用、结构及其源码分析、ThreadLocalMap

gao_tianLiang的博客 324

【2024年华为秋招-12月25日-第三题(300分)- PCB印刷电路板布线】(题目+思路+Java&C++&Python解析+在线测试)

在PCB印刷电路板设计中,器件之间的连线需要避免线路的阻抗值增大、而且赛件之间还有别的器件和别的干扰源,在布线时我们希望受到的干扰尽量小。现将电路板简化成一个M×N的矩阵,每个位置(单元格)的值表示其源干扰度。如果单元格的值为0,表示此位置没有干扰源;如果单元格的值为非0,则表示此位置是干扰源,其值为源干扰度。连线经过干扰源或干扰源附近会增加连线的总干扰度。位置Axy的干扰源的源干扰度为dd01、若连线经过位置Axy,则其总干扰度会增加d;2、若连线经过离位置A。

m0_73659489的博客 344

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

threadlocal用法_Thread、ThreadLocal源码解析

今天来看一下Thread和ThreadLocal类的源码。一、Thread(1)首先看一下线程的构造方法,之后会说每种参数的用法,而所有的构造函数都会指向init方法//空构造创建一个线程 Thread() //传入Runnable对象创建一个线程 Thread(Runnable target) //传入Runnable对象和线程名创建一个线程 Thread(Runnable target, ...

weixin_39757739的博客 108

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【面试精讲】ThreadLocal是什么,ThreadLocal源码分析,ThreadLocal应用,ThreadLocal内存泄漏

ThreadLocal是什么,ThreadLocal源码分析,ThreadLocal应用,ThreadLocal内存泄漏

小明的Java问道之路 5万+

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

ThreadLocal源码解析

有人说ThreadLocal性能真心差,但我觉得ThreadLocal的设计还是有很多值得学习的地方,也为我们考虑了很多,ThreadLocal性能真心差吗?不好说,如果项目中都是使用全局static变量来使用ThreadLocal,则不可能存在hash冲突,性能无与伦比,当大量在方法内部使用ThreadLocal时,这种情况可能就会出现hash冲突,但每次在set,get()元素的过程中,都会存在重置ThreadLocal的位置可能,在设置和查找过程中,性能真的那么差吗?

用心做文章,说人话 954

ThreadLocal源码详解

1.线程局部变量:ThreadLocal 提供了一种机制,使得每个线程可以独立地存储和访问变量,避免了线程间的干扰。2.实现机制:通过 ThreadLocalMap 存储每个线程的 ThreadLocal 变量,确保线程安全。3.父子线程关系:子线程不会继承父线程的 ThreadLocal 变量的值,但可以通过显式设置来使用,也可以通过inheritableThreadLocal。这种设计使得 ThreadLocal 在多线程编程中非常有用,尤其是在需要保持线程独立状态的场景中。

xweiran的博客 1066

ThreadLocal源码分析

数组中定位,如果定位的位置已经有了数据,则会将未过期的数据放到最靠近此位置的。遍历散列数组,从开始位置向后探测清理过期数据,将过期数据的。实现原理是子线程是通过在父线程中通过调用。沿途中碰到未过期的数据则将此数据。数据距离正确的桶的位置更近一些。的构造方法中被调用。

Sun的个人博客 1639

【JDK源码】线程系列之ThreadLocal

简介 ThreadLocal 这个类提供线程局部变量,这些变量与其他正常的变量的不同之处在于,每一个访问该变量的线程在其内部都有一个独立的初始化的变量副本; ThreadLocal实例变量通常采用 private static 在类中修饰。 只要 ThreadLocal 的变量能被访问,并且线程存活,那每个线程都会持有 ThreadLocal 变量的副本。当一个线程结束时,它所持有的所有 ThreadLocal 相对的实例副本都可被回收。 一句话说就是 ThreadLocal 适用于每个线程需要自己独立

早上真起不来的博客 1174

ThreadLocal 源码浅析

多线程在访问同一个共享变量时很可能会出现并发问题,特别是在多线程对共享变量进入写入时,那么除了加锁还有其他方法避免并发问题吗?本文将详细讲解 ThreadLocal 的使用及其源码。

Fuxing's Notes 1555

ThreadLocal源码深度详解

我们文章刚开始写的,定义ThreadLocal时定义一个匿名内部类,重写initialValue,这相当于是一个懒加载的过程,调用get方法时,如果没有设置过值,就会触发初始化过程;线程池中的核心线程都是复用的,通过下面的实例,上一个任务的ThreadLocal如果没有remove,下一个任务会拿到上一个任务的数据,抛开内存泄漏不说,这是一个非常危险的操作!我们使用下面的demo解释一下,使用0x61c88647这个黄金分割的数字,生成的下一个值,均衡的生成一个散列,并不会重复。

秃了也弱了 1170
上一篇: MySQL——查
下一篇: “数据结构”的个人理解
binaryshao
博客等级 码龄11年 52粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值