首页 > 编程语言 >ThreadLocal源码分析

ThreadLocal源码分析

时间:2024-06-12 19:31:09浏览次数:18  
标签:分析 Thread value ThreadLocal 源码 线程 key threadLocals

目录

0x00 ThreadLocal

ThreadLocal提供了线程局部的变量,但和普通局部变量不同,同一个ThreadLocal变量可以被多个线程共享,而不是线程私有的。

在ThreadLocal源代码中有一个使用例子,代码如下:

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
    new ThreadLocal<Integer>() {
        @Override protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    };

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

我们以这段代码为例,简单探索一下ThreadLocal的内部原理。

假设我们现在需要生成线程ID,则可以这么写:

public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(ThreadId.get());
        }).start();

        new Thread(() -> {
            System.out.println(ThreadId.get());
        }).start();
}

// 输出
// 0
// 1

为什么不同线程调用ThreadId.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 getMap(Thread t) {
    return t.threadLocals;
}

可以发现当一个线程调用get()时,会取出该线程内部的threadLocals变量,并以ThreadLocal对象为key取出map中对应的值。如果threadLocals为null,则再调用setInitialValue()方法,创建一个新的threadLocals变量。

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);
    }
    if (this instanceof TerminatingThreadLocal) {
        TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
    }
    return value;
}

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

protected T initialValue() {
    return null;
}

在调用setInitialValue()方法时需要调用initialValue()获得一个初值,默认情况下是null。因此在开头的例子中也重写了这个initialValue()来为ThreadLocal变量提供初值。

既然有get()方法,同样也有set()方法,内部仅仅是调用了map的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对象内部都会有一个成员变量threadLocals,这实质上是一个哈希表,它以ThreadLocal为key,而value的类型是不固定的,取决于ThreadLocal对象声明时的泛型。
当同一个ThreadLocal对象被多个线程访问时,每个线程都会创建一个键值对并放入自己的threadLocals变量内,这个键值对的key是该ThreadLocal对象,而值则是由ThreadLocal对象的initialValue()提供。除了一个ThreadLocal对象可以被多个线程访问,一个线程内的threadLocals也可以同时持有多个ThreadLocal对象,因此线程和ThreadLocal是多对多的关系。

回顾开头提到的,同一个ThreadLocal对象被多个线程共享但仍然线程安全的原因在于,这个ThreadLocal对象仅仅是作为一个只读的key存放在每个线程的threadLocals内部,而它对应的值则是以副本的形式作为键值对的value,每个线程的threadLocals以相同的ThreadLocal作为key,但是值是不共享的。

0x01 ThreadLocalMap

在前一节中,我们发现ThreadLocal仅仅是存在每个线程的threadLocals里,threadLocals是一个ThreadLocalMap对象,我们简单探索一下它的内部。

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    private Entry[] table;
    
    // ...
}

可以发现ThreadLocalMap是一个哈希表,并且采用线性探测法来解决冲突。其内部类Entry继承了弱引用,因此每个entry的key是对ThreadLocal对象的弱引用,而value则是强引用。

这里其实有些令人疑惑,为什么Entry类要对key使用弱引用而不是强引用呢?在源代码中我发现这样一段注释:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.
原意大概是说,为了帮助处理那些大且长时间存活的对象,因此对key进行弱引用,但是由于没有使用到引用队列,因此这些过期的entry只有在哈希表快用完时才保证一定删除。实际上,在ThreadLocalMap的getEntry或setEntry方法中,只要检测到某个key为null,就会将对应的value引用置为null,让GC能够及时回收。

因此我们可以想象,当某一个ThreadLocal对象不再使用了,那么所有线程中关于该ThreadLocal对象的value也会被回收。这里涉及到弱引用的概念,如果某个对象仅仅存在弱引用,那么GC一旦发现该对象,无论在内存是否充足的情况下都会立刻回收它。

0x02 ThreadLocal内存泄漏

前面我们提高,ThreadLocal类对象一旦不再使用,其对应的value在各个线程中也会被回收。然而,实际使用中我们通常将ThreadLocal类对象设为static,这就意味着整个程序运行过程中它都会一直存活,那么只要该线程不结束,即使某一个ThreadLocal对象不再使用了,线程内部的threadLocals始终不会被回收。这就引起了内存泄漏问题。

要解决这个问题也很简单,就是一旦ThreadLocal对象在某个线程中不再使用了,就调用remove方法去手动删除对应的entry。

/**
 * Remove the entry for key.
 */
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.refersTo(key)) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

标签:分析,Thread,value,ThreadLocal,源码,线程,key,threadLocals
From: https://blog.csdn.net/weixin_44224167/article/details/139622368

相关文章

  • [FEM-7]杆单元分析的MATLAB程序
    目录11D杆单元的有限元分析程序(Bar1D2Node)1.1单元刚度矩阵1.2整体刚度矩阵1.2.1组装第1根杆件1.2.2组装第2根杆件1.2.3组装第3根杆件1.3单元应力1.4单元节点力矢量2平面(2D)杆单元的有限元分析程序(Bar2D2Node)2.1单元刚度矩阵2.2整体刚度矩阵2.2.1组装......
  • 2024年春季学期《算法分析与设计》练习15
    A:简单递归求和题目描述使用递归编写一个程序求如下表达式前n项的计算结果: (n<=100)1- 3+5-7+9-11+......输入n,输出表达式的计算结果。输入多组输入,每组输入一个n,n<=100。输出输出表达式的计算结果。样例输入 Copy12样例输出 Copy1-2#pragma......
  • 分析GIS在疾病传播模型和公共卫生决策中的作用
    在这个全球化日益加深的时代,疾病的跨国界传播成为全球公共卫生面临的重大挑战。地理信息科学(GIS)作为一门集成了空间数据采集、处理、分析及可视化的技术体系,在公共健康领域展现出其不可替代的价值。本文旨在深入探讨GIS如何助力于疾病传播模型的构建以及在制定公共卫生决策中扮......
  • 【第三篇】SpringSecurity请求流程分析
    简介本篇文章主要分析一下SpringSecurity在系统启动的时候做了那些事情、第一次请求执行的流程是什么、以及SpringSecurity的认证流程是怎么样的,主要的过滤器有哪些?SpringSecurity初始化流程1.加载配置文件web.xml当Web服务启动的时候,会加载我们配置的web.xml文件web.xml......
  • Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(2)
    我们继续分析Chromium的Mojo模块。DispatcherDispatcher是MojoIPC系统中的一个关键概念。它是一个虚基类类(或接口),用于实现与特定MojoHandle相关联的Mojo核心API调用。在Mojo系统中,应用程序通过这些API与各种类型的IPC机制进行交互,如消息管道、共享缓冲区......
  • 咖啡行业研究(人群画像、市场、品牌分析)
    2024年上海咖啡市场调研报告.pdf-报告查一查2024中国现制咖啡风味图谱-KERRY-饿了么.pdf-报告查一查2024中国城市咖啡发展报告-上海市文化创意产业促进会-202405.pdf-报告查一查瑞幸咖啡(LKNCY.US)美股公司首次覆盖报告:大众现磨咖啡引领者,乘行业东风顺势崛起.pdf-报告查一查......
  • 青否数字人直播源码代理端后台操作步骤!
    青否数字人直播源码代理端后台,我们将详细介绍一下数字人的代理端后台的详细操作步骤!1.代理端入口2.代理后台预览基本设置,账号管理,资金管理,克隆端。2.1基本设置设置一些账号的基本信息包括名称,logo,二维码等等,点击“提交”即可2.2账号管理打开账号管理,可搜索已创建的......
  • 蒸发温差热机-大号尺寸饮水鸟发电应用原理详细分析
    蒸发温差热机-大号尺寸饮水鸟发电应用原理详细分析作为物联网数据采集解决方案专业提供商,数采物联网小编daq-iot在这里做以下内容介绍,并诚挚的欢迎大家讨论和交流  饮水鸟是一种热机,它利用温差将热能转换为设备内的压差,并执行机械做功。像所有热机一样,饮水鸟通过热力学循......
  • C#(asp.net) 非物质文化遗产建档管理系统设-计算机毕业设计源码91695
    摘 要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对非物质文化遗产建档管理系统等问题,对非物质文化遗产建档管理系统进行研究分析,然后开发设计出......
  • 最新AI系统+ChatGPT网站H5源码+AI绘画系统,DALL-E3文生图,详细图文搭建教程/文档分析/识
    目录一、文章前言系统文档 二、系统演示三、系统功能模块3.1AI全模型支持/插件系统AI模型提问文档分析​识图理解能力3.2GPts应用3.2.1GPTs应用3.2.2GPTs工作台3.2.3自定义创建预设应用3.3AI专业绘画3.3.1文生图/图生图(垫图)3.3.2 局部编辑重绘3.3.3 ......