文章目录
一、ThreadLocal使用案例
二、ThreadLocal类的实现原理
2.1 核心方法set()
2.2 核心方法get()
2.3 核心方法remove()
三、ThreadLocal.ThreadLocalMap结构分析
四、ThreadLocal内存泄漏问题
参考资料
维持线程封闭性可以通过Ad-hoc线程封闭、栈封闭来实现,一种更加规范的方法是使用ThreadLocal类。ThreadLocal类提供线程局部变量,通过get、set等方法访问变量,为每个使用该变量的线程创建一个独立的副本。

一、ThreadLocal使用案例
案例中只开启了一个线程threadA,展示了在线程内部设置、获取、清除局部变量。

public class ThreadLocalTest {
    // 初始化ThreadLocal变量
    static ThreadLocal<String> localVariable = new ThreadLocal<>();

static void print(String str) {
        // 打印当前线程本地内存中的变量值
        System.out.println(str + ": " + localVariable.get());
        // 清除当前线程本地内存中的变量
        localVariable.remove();
    }

public static void main(String[] args) {
        // 创建线程A
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                // 设置线程A中的本地变量的值
                localVariable.set("threadA localVariable");
                print("threadA");
                // 获取线程A中的本地变量的值
                System.out.println("threadA remove after: " + localVariable.get());
            }
        });
        // 启动线程
        threadA.start();
    }
}

运行结果:
threadA: threadA localVariable
threadA remove after: null

案例二:线程唯一标识符生成器,为每个调用ThreadId.get()方法的线程创建id。

public class ThreadId {
    // 下一个要被分配的线程id
    private static final AtomicInteger nextId = new AtomicInteger(0);

// 线程局部变量
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

// 返回当前线程唯一的id
    public static int get() {
        return threadId.get();
    }
}

二、ThreadLocal类的实现原理
在Thread类中有一个threadLocals成员变量,其类型是ThreadLocalMap,默认情况下为null。

ThreadLocal.ThreadLocalMap threadLocals = null;
1
当某线程首次调用ThreadLocal变量的get或set方法时,会进行对象创建。在线程退出时,当前线程的threadLocals变量被清空。

private void exit() {
    ...
    threadLocals = null;
    inheritableThreadLocals = null;
    ...
}

每个线程的局部变量不是存放于ThreadLocal实例中,而是存放于线程的threadLocals变量,即线程内存空间中。threadLocals变量本质上是Map数据结构,可以存放多个ThreadLocal变量键值对。

【助解】ThreadLocal类可以看出一个外壳,线程中调用某ThreadLocal变量的set方法可以将变量值放入到该线程的threadLocals变量中,数据格式是<当前线程中该ThreadLocal变量的this引用,变量值>。当调用线程调用ThreadLocal变量的get方法时,从当前线程的threadLocals变量中取出key(引用)对应的value值。

2.1 核心方法set()
将ThreadLocal变量的当前线程副本的值设置为指定value值。

public void set(T value) {
    // 获取调用方法的当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程自身的threadLocals变量
    ThreadLocalMap map = getMap(t);
    if (map != null) 
        // map不为空,则设置
        map.set(this, value);
    else 
        // map为空,说明第一次调用,初始化线程的threadLocals变量
        createMap(t, value);
}

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

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

线程的threadLocals变量,即ThreadLocal.ThreadLocalMap,是HashMap结构,它的key是当前ThreadLocal的实例对象引用,value值是该ThreadLocal实例对象调用set方法设置的值。

2.2 核心方法get()
返回ThreadLocal变量在当前线程副本中的值。如果当前线程中没有该变量的值,返回值会被首次初始化为initialValue()方法的值。

public T get() {
    // 获取当前线程以及其threadLocals变量
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 如果threadLocals变量不为空
    if (map != null) {
        // 根据当前ThreadLocal对象应用获取Entry,存在则直接返回value值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // threadLocals为空,初始化当前线程threadLocals变量
    return setInitialValue();
}

// threadLocals存在,设置初始值;不存在,初始化threadLocals变量
private T setInitialValue() {
    // 返回当前ThreadLocal变量的当前线程初始值
    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;
}

2.3 核心方法remove()
当前线程threadLocals变量存在的话,删除当前线程的ThreadLocal实例对象。

public void remove() {
    ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
    if (var1 != null) {
        var1.remove(this);
    }
}

三、ThreadLocal.ThreadLocalMap结构分析
ThreadLocalMap内部类实现细节,由于内容较多独立成了一篇博客。ThreadLocal.ThreadLocalMap实现细节

四、ThreadLocal内存泄漏问题
每个线程的ThreadLocal变量都存放在该线程的threadLocals变量中,如果当前线程一直不退出,这些ThreadLocal变量会一直存在,因此可能会导致内存泄漏。通过调用ThreadLocal类的remove方法避免这一问题。

ThreadLocalMap中采用ThreadLocal弱引用作为Entry的key,如果一个ThreadLocal没有外部强引用来引用它,下一次系统GC时,这个ThreadLocal必然会被回收,ThreadLocalMap中就会出现key为null的Entry。

ThreadLocal类的set、get、remove方法都可能触发对key为null的Entry清理操作。expungeStaleEntry方法会清空Entry及其value,Entry会在下次GC被回收。

如果当前线程一直在运行,并且一直不执行get、set、remove方法,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,导致这些key为null的Entry的value永远无法回收,造成内存泄漏。

参考资料
《Java并发编程实战》
《Java并发编程之美》

快速搞懂ThreadLocal实现原理相关推荐

  1. php service原理,轻松搞懂WebService工作原理

    用更简单的方式给大家谈谈WebService,让你更快更容易理解,希望对初学者有所帮助. WebService是基于网络的.分布式的模块化组件. 我们直接来看WebService的一个简易工作流程: ...

  2. 快速搞懂平面设计视觉思维的窍门

    在这个商业氛围很浓的社会中,各种设计海报让人眼花缭乱,如何脱颖而出?需要靠设计的视觉冲击力.所以做平面设计中,要掌握好视觉设计思维,才能更胜一筹.这里给大家讲几个小窍门,让你们快速搞懂平面设计视觉思维 ...

  3. 一文快速搞懂Kudu到底是什么

    文章目录 引言 文章传送门: Kudu 介绍 背景介绍 新的硬件设备 Kudu 是什么 Kudu 应用场景 Kudu 架构 数据模型 分区策略 列式存储 整体架构 Kudu Client 交互 Kud ...

  4. 一文快速搞懂对95%置信区间的理解

    一文快速搞懂对95%置信区间的理解 综合知乎上各大神的解答和网络资料得到本文对95%置信区间的理解 先给出结论 最常出现的对置信区间的错误理解: 在95%置信区间内,有95%的概率包括真实参数  (错 ...

  5. 5个品牌案例,6张优质模板,帮你快速搞懂「商业模式画布」!

    新入职.新行业,新人如何快速搞懂它的业务模式?新领域.新业务,投资人如何快速搞明白一个公司?新商机.新模式,创业者如何快速一个业务的商业前景? 推荐大家使用商业模式画布,它可以让你轻松看透商业模式.对 ...

  6. Android 动态分区详解(一) 5 张图让你搞懂动态分区原理

    文章目录 0. 导读 1. 动态分区详解的背景 1.1 背景 1.2 动态分区的本质 2. Linux device mapper 驱动 3. Android 动态分区布局 3.1 动态分区布局 3. ...

  7. 快速搞懂Lombok使用与原理

    1 简介 Lombok是一款好用顺手的工具,就像Google Guava一样,在此予以强烈推荐,每一个Java工程师都应该使用它.Lombok是一种Java™实用工具,可用来帮助开发人员消除Java的 ...

  8. 别不承认!搞懂那些数理原理,才发现它们和枯燥根本不沾边!

    ▲ 点击查看 数理化的学习对于很多孩子,包括家长都是一个大难题. 比如,我们要教孩子认识动物,一般是要给孩子看动物的图片或实体,孩子自然就对这个动物有个认知. 要教孩子数字,就会用一件玩具.两个苹果这 ...

  9. 怎么从转移特性曲线上看dibl_「科普向」这篇让你快速搞懂IGBT的静态特性

    IGBT的静态特性其实并非难以理解的东西,即便是对于外行人而言. 刚接触那会儿,看到转移特性.输出特性之类的就想溜之大吉,加之网上查询的资料一概笼统简单,只描述特性曲线所表示的关系结果,却并不解释曲线 ...

最新文章

  1. P4735 最大异或和(可持久化trie树、求最大区间异或和)
  2. 最短路径问题 java实现 源代码
  3. 数字转换为字符的L受哪个参数影响
  4. java 调用autoit_Java中调用AutoIt操作控件
  5. 划时代的项目管理核心引擎——DynamicGantt 动态图甘特图
  6. Cocos2d-x 3.0正式版及android环境搭建
  7. 很多工程师问ESP32彩屏能不能在arduino上面来玩,这个是没有问题的
  8. hdu 3308 LCIS 线段树 + 区间合并
  9. mysql5.7下载及详细安装教程_MySQL 5.7 下载及安装教程(详细)
  10. 前端开发工具介绍----合成雪碧图工具(css sprite)
  11. ppc手机用蓝牙和电脑同步上网设置教程
  12. 多旋翼无人机动力系统发展历程
  13. 高通WLAN框架学习(3)- -WLAN FTM 模式
  14. 怎么恢复计算机系统软件,重装系统后软件如何恢复原状
  15. [树莓派 PICO(基于MicroPython)]基础教程02-按键测试、按键控制外设LED开关
  16. 类加载顺序及加载过程详解
  17. sw运行很卡怎么办_win10运行solidworks好卡怎么解决_win10打开solidworks经常卡顿如何处理...
  18. Win10开启telnet功能
  19. 全国等级保护测评机构推荐目录
  20. 通用 Mapper @KeySql 注解 genId 方法详解

热门文章

  1. Android 自动向上滚动,android – Recyclerview在插入数据时自动向上滚动
  2. c语言去尾法和进一法的例子,《去尾法与进一法》教学案例与反思
  3. 堆排序算法c语言筛选法,【排序】排序算法之选择排序
  4. ajax给data赋值,vue 2.0 methods 里ajax生成的数据,怎么赋值给data
  5. Markdown编辑表格实现合并单元格、单元格内容换行
  6. VMWare下虚拟机ubuntu与宿主机windows文件共享
  7. 太原学计算机的职高,山西太原职高学校排名
  8. 教室工资管理系统c语言课程设计csdn,工资管理系统(C编写)
  9. php no input file specified.,nginx+php出现No input file specified解决办法
  10. java委托机制教程_通过反射实现Java下的委托机制代码详解