0

0

ThreadLocal的实现原理的分析介绍(附代码)

不言

不言

发布时间:2019-02-16 13:37:47

|

3158人浏览过

|

来源于博客园

转载

本篇文章给大家带来的内容是关于threadlocal的实现原理的分析介绍(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

ThreadLocal,即线程局部变量,用来为每一个使用它的线程维护一个独立的变量副本。这种变量只在线程的生命周期内有效。并且与锁机制那种以时间换取空间的做法不同,ThreadLocal没有任何锁机制,它以空间换取时间的方式保证变量的线程安全。

本篇从源码方面分析ThreadLocal的实现原理。

先看一下ThreadLocal类图结构

  

SuppliedThreadLocal主要是JDK1.8用来扩展对Lambda表达式的支持,有兴趣的自行百度。

ThreadLocalMap是ThreadLocal的静态内部类,也是实际保存变量的类。

Entry是ThreadLocalMap的静态内部类。ThreadLocalMap持有一个Entry数组,以ThreadLocal为key,变量为value,封装一个Entry。

下面以一张图简要说明Thread,ThreadLocal,ThreadLocalMap和Entry的关系。

  

说明一下上图:

  1. 一个Thread拥有一个ThreadLocalMap对象

  2. ThreadLocalMap拥有一个Entry数组

  3. 每个Entry都有k--v

  4. Entry的key就是某个具体的ThreadLocal对象

下面分析主要方法。

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

这里可以看出:一个Thread只拥有一个ThreadLocalMap对象;具体存值调用的是ThreadLocalMap的set(),传入的参数key就是当前ThreadLocal对象。

再看看ThreadLocalMap的set()方法:

private void set(ThreadLocal key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); // 1

            for (Entry e = tab[i];  // 2
                 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); // 3
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
                rehash();
        }

  1. 通过key的hashCode与数组容量 -1 取模,计算数组index

  2. 从当前index开始遍历,清除key为null的无效Entry

    LOVESTUdio多校园网络店铺
    LOVESTUdio多校园网络店铺

    主要更新介绍: 完美整合Discuz!论坛,实现一站式登陆、退出、注册; 同步所有会员资料; 新增购物车功能,商品购买更加方便、快捷; 新增部分快捷菜单,网站访问更加方便; 限制首页商品、店铺标题显示长度; 修正会员后台管理不能更改密码的错误; 完善商品显示页面所有功能链接; 修正后台标签管理部分错误; 修正前台学校列表不按后台顺序显示的错误; 修正搜索功能中学校名称过长导致显示紊乱的现象; 修正

    下载
  3. 将K-V封装为Entry,并放入数组

  4. 判断是否需要进行Entry数组扩容。threshold的值为数组容量的2/3。

  看看扩容的resize()方法:

private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

这里主要就是扩容为原先的2倍。然后遍历旧数组,根据新数组容量重新计算Entry在新数组中的位置。

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

ThreadLocalMap的getEntry()方法如下:

private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1); // 1
            Entry e = table[i];
            if (e != null && e.get() == key) // 2
                return e;
            else
                return getEntryAfterMiss(key, i, e); //3
        }

private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) { //4
                ThreadLocal k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

  1. 计算index

  2. 当前index上的Entry不为空且key相同,直接返回

  3. 否则去相邻index寻找

  4. 循环查找,发现无效key就清除。找到就结束循环。

3、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();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

处理方式和查找保存类似,删除对应Entry后都会去除key为null的无效元素。

注意

static class Entry extends WeakReference> {}

ThreadLocal可能存在OOM问题。因为ThreadLocalMap是使用ThreadLocal的弱引用作为key的,发生GC时,key被回收,这样我们就无法访问key为null的value元素,如果value本身是较大的对象,那么线程一直不结束的话,value就一直无法得到回收。特别是在我们使用线程池时,线程是复用的,不会杀死线程,这样ThreadLocal弱引用被回收时,value不会被回收。

在使用ThreadLocal时,线程逻辑代码结束时,必须显示调用ThreadLocal.remove()方法。

相关专题

更多
C++ 高级模板编程与元编程
C++ 高级模板编程与元编程

本专题深入讲解 C++ 中的高级模板编程与元编程技术,涵盖模板特化、SFINAE、模板递归、类型萃取、编译时常量与计算、C++17 的折叠表达式与变长模板参数等。通过多个实际示例,帮助开发者掌握 如何利用 C++ 模板机制编写高效、可扩展的通用代码,并提升代码的灵活性与性能。

10

2026.01.23

php远程文件教程合集
php远程文件教程合集

本专题整合了php远程文件相关教程,阅读专题下面的文章了解更多详细内容。

29

2026.01.22

PHP后端开发相关内容汇总
PHP后端开发相关内容汇总

本专题整合了PHP后端开发相关内容,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

php会话教程合集
php会话教程合集

本专题整合了php会话教程相关合集,阅读专题下面的文章了解更多详细内容。

21

2026.01.22

宝塔PHP8.4相关教程汇总
宝塔PHP8.4相关教程汇总

本专题整合了宝塔PHP8.4相关教程,阅读专题下面的文章了解更多详细内容。

13

2026.01.22

PHP特殊符号教程合集
PHP特殊符号教程合集

本专题整合了PHP特殊符号相关处理方法,阅读专题下面的文章了解更多详细内容。

11

2026.01.22

PHP探针相关教程合集
PHP探针相关教程合集

本专题整合了PHP探针相关教程,阅读专题下面的文章了解更多详细内容。

8

2026.01.22

菜鸟裹裹入口以及教程汇总
菜鸟裹裹入口以及教程汇总

本专题整合了菜鸟裹裹入口地址及教程分享,阅读专题下面的文章了解更多详细内容。

55

2026.01.22

Golang 性能分析与pprof调优实战
Golang 性能分析与pprof调优实战

本专题系统讲解 Golang 应用的性能分析与调优方法,重点覆盖 pprof 的使用方式,包括 CPU、内存、阻塞与 goroutine 分析,火焰图解读,常见性能瓶颈定位思路,以及在真实项目中进行针对性优化的实践技巧。通过案例讲解,帮助开发者掌握 用数据驱动的方式持续提升 Go 程序性能与稳定性。

9

2026.01.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号