热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

JavaThreadLocal原理分析

JavaThreadLocal原理分析-1简述ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而
1 简述

ThreadLocal 提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何时候访问这个本地变量的结果都是一致的。当此线程结束生命周期时,所有的线程本地实例都会被 GC。ThreadLocal 相当于提供了一种线程隔离,将变量与线程相绑定。

2 原理

ThreadLocal是一种数据结构,有点像map,但是一个ThreadLocal只能保存一个。

每个线程有一个 ThreadLocalMap 成员变量,本质是一个 map。map 里存储的 key 是一个弱引用,其包装了当前线程中构造的 ThreadLocal 对象,这意味着,只要 ThreadLocal 对象丢掉了强引用,那么在下次 GC 后,map 中的 ThreadLocal 对象也会被清除,对于那些ThreadLocal 对象为空的 map 元素,会当为垃圾,稍后会被主动清理。map 里存储的 value 就是缓存到当前线程的值,这个 value 没有弱引用去包装,需要专门的释放策略。

3 核心方法

get()

/**
 * 返回当前 ThreadLocal 对象关联的值
 *
 * @return
 */
public T get() {
    // 返回当前 ThreadLocal 所在的线程
    Thread t = Thread.currentThread();
    // 从线程中拿到 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 从 map 中拿到 entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 如果不为空,读取当前 ThreadLocal 中保存的值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    // 若 map 为空,则对当前线程的 ThreadLocal 进行初始化,最后返回当前的 ThreadLocal 对象关联的初值,即 value
    return setInitialValue();
}

set()

/**
 * 为当前 ThreadLocal 对象关联 value 值
 *
 * @param value 要存储在此线程的线程副本的值
 */
public void set(T value) {
    // 返回当前 ThreadLocal 所在的线程
    Thread t = Thread.currentThread();
    // 返回当前线程持有的map
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 如果 ThreadLocalMap 不为空,则直接存储键值对
        map.set(this, value);
    } else {
        // 否则,需要为当前线程初始化 ThreadLocalMap,并存储键值对 
        createMap(t, value);
    }
}

remove()

/**
 * 清理当前 ThreadLocal 对象关联的键值对
 */
public void remove() {
    // 返回当前线程持有的 map
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        // 从 map 中清理当前 ThreadLocal 对象关联的键值对
        m.remove(this);
    }
}

可以发现,不管是get、set、remove操作的都是当前线程持有的ThreadLocalMap。
每个线程都有一个自己的ThreadLocalMap。
所以如果线程A set 值到map中,线程B又 set ,两者是不会互相影响的。

4 ThreadLocalMap

最后,我们看看ThreadLocalMap的实现。

/**
 * 键值对实体的存储结构
 */
static class Entry extends WeakReference> {
    //当前线程关联的 value,这个 value 并没有用弱引用追踪
    Object value;
    /**
     * 构造键值对
     *
     * @param k k 作 key,作为 key 的 ThreadLocal 会被包装为一个弱引用
     * @param v v 作 value
     */
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

WeakReference保证GC时能够回收线程set的值。

// 返回 key 关联的键值对实体
private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // 若 e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回
    if (e != null && e.get() == key) {
        return e;
    } else {
        // 从 i 开始向后遍历找到键值对实体
        return getEntryAfterMiss(key, i, e);
    }
}

// 在 map 中存储键值对
private void set(ThreadLocal key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 计算 key 在数组中的下标
    int i = key.threadLocalHashCode & (len - 1);
    // 遍历一段连续的元素,以查找匹配的 ThreadLocal 对象
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        // 获取该哈希值处的ThreadLocal对象
        ThreadLocal k = e.get();
        // 键值ThreadLocal匹配,直接更改map中的value
        if (k == key) {
            e.value = value;
            return;
        }
        // 若 key 是 null,说明 ThreadLocal 被清理了,直接替换掉
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 直到遇见了空槽也没找到匹配的ThreadLocal对象,那么在此空槽处安排ThreadLocal对象和缓存的value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 如果没有元素被清理,那么就要检查当前元素数量是否超过了容量阙值(数组大小的三分之二),以便决定是否扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold) {
        // 扩容的过程也是对所有的 key 重新哈希的过程
        rehash();
    }
}

从源码中我们可以看到,ThreadLocalMap和HashMap有点相似,但是ThreadLocalMap没有采用数组+链表(红黑树)的方式解决hash冲突,而是采用线性探测寻址法

5 总结

每个Thread都有一个ThreadLocalMap成员变量,这个变量和ThreadLocal绑定在一起,很好的给线程提供了独享数据的地方。
对比多线程资源共享,采用同步机制(以时间换空间),此方式是以空间换时间,性能比较好。
不过使用ThreadLocal的时候,还需要注意内存泄露的问题,虽然是采用WeakReference,但是如果线程一直运行还是可能导致内存泄露,所以最好是使用完后 remove 一下。


推荐阅读
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文详细介绍如何使用Python进行配置文件的读写操作,涵盖常见的配置文件格式(如INI、JSON、TOML和YAML),并提供具体的代码示例。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文详细介绍了如何构建一个高效的UI管理系统,集中处理UI页面的打开、关闭、层级管理和页面跳转等问题。通过UIManager统一管理外部切换逻辑,实现功能逻辑分散化和代码复用,支持多人协作开发。 ... [详细]
  • 本文探讨了 Objective-C 中的一些重要语法特性,包括 goto 语句、块(block)的使用、访问修饰符以及属性管理等。通过实例代码和详细解释,帮助开发者更好地理解和应用这些特性。 ... [详细]
  • 本文探讨了如何优化和正确配置Kafka Streams应用程序以确保准确的状态存储查询。通过调整配置参数和代码逻辑,可以有效解决数据不一致的问题。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.xmlbeans.SchemaType 类的 getBaseEnumType() 方法,提供了多个代码示例,并解释了其在不同场景下的使用方法。 ... [详细]
  • 最近团队在部署DLP,作为一个技术人员对于黑盒看不到的地方还是充满了好奇心。多次咨询乙方人员DLP的算法原理是什么,他们都以商业秘密为由避而不谈,不得已只能自己查资料学习,于是有了下面的浅见。身为甲方,虽然不需要开发DLP产品,但是也有必要弄明白DLP基本的原理。俗话说工欲善其事必先利其器,只有在懂这个工具的原理之后才能更加灵活地使用这个工具,即使出现意外情况也能快速排错,越接近底层,越接近真相。根据DLP的实际用途,本文将DLP检测分为2部分,泄露关键字检测和近似重复文档检测。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • Java 类成员初始化顺序与数组创建
    本文探讨了Java中类成员的初始化顺序、静态引入、可变参数以及finalize方法的应用。通过具体的代码示例,详细解释了这些概念及其在实际编程中的使用。 ... [详细]
  • 本文详细介绍了Python中文件的基本操作,包括打开、读取、写入和关闭文件的方法,并通过实例展示了如何将Excel文件转换为CSV文件以及进一步转换为HTML文件。此外,还涉及了成绩等级替换的具体实现。 ... [详细]
  • 本文详细探讨了JDBC(Java数据库连接)的内部机制,重点分析其作为服务提供者接口(SPI)框架的应用。通过类图和代码示例,展示了JDBC如何注册驱动程序、建立数据库连接以及执行SQL查询的过程。 ... [详细]
author-avatar
寒空动烟雪
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有