首页 > 编程语言 >深入理解 Java 中的 ThreadLocal

深入理解 Java 中的 ThreadLocal

时间:2023-06-24 23:44:26浏览次数:37  
标签:Java Thread ThreadLocalMap ThreadLocal 深入 线程 null local

1. 什么是 ThreadLocal

在 Java 多线程编程中,我们经常会遇到共享变量的并发访问问题。为了解决这个问题,Java 提供了 ThreadLocal 类,它允许我们在每个线程中存储和访问线程局部变量,而不会影响其他线程的数据。

2. 使用 ThreadLocal

使用 ThreadLocal 很简单,我们只需要创建一个 ThreadLocal 对象,然后使用 set() 方法设置值,使用 get() 方法获取值即可。

2.1 两个线程使用一个ThreadLoal变量

点击查看代码
public static void main(String[] args) {
        ThreadLocal<String> local = new ThreadLocal<>();

        Thread t1 = new Thread(() -> {
            local.set("t1");
            System.out.println("tid=" + Thread.currentThread() + ", local=" + local + ",local val =" + local.get());
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            local.set("t2");
            System.out.println("tid=" + Thread.currentThread() + ", local=" + local + ",local val =" + local.get());
        });
        t2.start();
    }

运行结果

从以上执行结果可以看出,创建的ThreadLocal变量 local在线程t1和t2中是同一个变量,但是通过set()方法设置数据后,保存的却是各自的副本,再使用get()方法访问的是各自的数据,实现了不同线程间数据的隔离。

2.2 单个线程有两个ThreadLoal变量

点击查看代码
public static void main(String[] args) {
        ThreadLocal<String> local1 = new ThreadLocal<>();
        ThreadLocal<String> local2 = new ThreadLocal<>();

        Thread t1 = new Thread(() -> {
            local1.set("v1");
            local2.set("v2");
            System.out.println("tid=" + Thread.currentThread() + ", local1=" + local1 + ", val =" + local1.get());
            System.out.println("tid=" + Thread.currentThread() + ", local1=" + local2 + ", val =" + local2.get());

        });
        t1.start();
    }

运行结果:

线程可以有多个threadLocal变量,他们之间也是相互独立的。

3. 实现原理

ThreadLocal 的实现原理主要涉及三个关键点:ThreadLocal 类、Thread 类和 ThreadLocalMap 类。

  • ThreadLocal 类是 ThreadLocal 变量的容器,每个线程中可以定义多个 ThreadLocal 对象。
  • Thread 类是 Java 中表示线程的类,每个线程都有一个 ThreadLocalMap 对象。
点击查看代码
public
class Thread implements Runnable {
   
    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;

    /* omit some. */


    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  • ThreadLocalMap 类是 ThreadLocal 对象的存储结构,它是一个特定于线程的键值对集合。
点击查看代码
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;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

当我们使用 ThreadLocal 设置值时,值被存储在当前线程的 ThreadLocalMap 中。在当前线程中,我们可以通过 ThreadLocal 对象来获取和更新这些值,而不会干扰其他线程的数据。

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

ThreadLocal的set()方法在执行时,首先获取当前线程中对应的 ThreadLocalMap 对象。

如果当前线程的 ThreadLocalMap 为 null,则需要先进行初始化。调用 createMap() 方法创建一个新的 ThreadLocalMap 对象,并将其设置到当前线程中。

ThreadLocalMap 是一个自定义的哈希表结构,用于存储 ThreadLocal 对象和对应的值。在 ThreadLocalMap 中,ThreadLocal 实例是弱引用,而值则是强引用。

接下来,set() 方法会将根据当前 ThreadLocal 实例作为键,将传入的值作为值,创建出一个Entry,存储到 ThreadLocalMap 的Entry[] tab数组中。

ThreadLocalMap 使用线性探测法解决哈希冲突,并使用开放地址法进行存储。当插入新的键值对时,会遍历数组,找到合适的位置进行插入。如果发生哈希冲突,则会继续向后查找空槽进行插入。

存储完成后,当前线程的 ThreadLocalMap 中就包含了该 ThreadLocal 实例及其对应的值。

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

get() 方法会获取当前线程中的 ThreadLocalMap 对象

如果当前线程的 ThreadLocalMap 为 null,表示当前线程没有使用 ThreadLocal,则直接返回 null。

如果当前线程的 ThreadLocalMap 不为 null,则通过当前 ThreadLocal 实例作为键,从 ThreadLocalMap 中获取对应的值。 从key.threadLocalHashCode & (table.length - 1)可知ThreadLocal 实例在每个线程中Entry[]table数组的下标是固定的。

在 ThreadLocalMap 中,会根据 ThreadLocal 实例的哈希值进行索引,查找对应的存储位置。

如果找到对应的位置,即表示当前线程已经使用过该 ThreadLocal 实例,可以直接返回存储的值。

如果未找到对应的位置,或者位置对应的 ThreadLocal 实例与当前 ThreadLocal 实例不匹配,即表示当前线程没有使用过该 ThreadLocal 实例,返回 null。

3.3 三者之间的关系图

4. ThreadLocal 的应用场景

ThreadLocal 在许多场景下非常有用,特别是在以下情况下:

  • 多线程环境下的数据隔离:当多个线程需要访问同一个对象的数据时,使用 ThreadLocal 可以避免线程间的数据竞争和并发访问问题。
  • 线程上下文传递:在跨线程的业务逻辑中,可以使用

5. 注意事项

  • 内存泄漏问题:由于 ThreadLocal 使用了线程的唯一标识作为索引,在使用完毕后,如果没有手动清理或及时移除 ThreadLocal 对象的引用,可能会导致内存泄漏问题。确保在使用完毕后及时调用 remove() 方法或将 ThreadLocal 对象设置为 null。

  • 初始值设置:每个线程在第一次访问 ThreadLocal 对象时,会调用 initialValue() 方法来获取初始值。如果需要特定的初始值,可以通过继承 ThreadLocal 并重写 initialValue() 方法来实现。

  • 共享变量问题:虽然 ThreadLocal 可以在每个线程中存储自己的数据,但要注意共享变量的访问。如果多个线程共享同一个对象,并且这个对象中包含 ThreadLocal 变量,那么多个线程对 ThreadLocal 变量的修改会相互影响。确保对共享变量的访问是线程安全的。

  • 使用场景选择:ThreadLocal 应该谨慎使用,只在确实需要在每个线程中存储和访问数据时使用。滥用 ThreadLocal 可能导致代码的复杂性增加,并且可能不利于代码的维护和理解。

  • 清理操作:在使用 ThreadLocal 时,需要确保在合适的时机进行清理操作。当线程执行完毕或不再需要使用 ThreadLocal 时,应该手动调用 remove() 方法来清理 ThreadLocal 对象,以避免潜在的内存泄漏问题。

总之,使用 ThreadLocal 需要谨慎并遵循最佳实践,确保正确管理和使用 ThreadLocal 对象,以实现线程间的数据隔离和安全访问

标签:Java,Thread,ThreadLocalMap,ThreadLocal,深入,线程,null,local
From: https://www.cnblogs.com/techs/p/17500447.html

相关文章

  • [java] 利用反射,将对象A中与对象B中字段名相同的属性值赋予对象B
    前言:最近开发遇到了这样一个需求,前端提交的表单对应类是origin,但后端数据库表对应类是target,两者中有重合字段,origin类中有待处理字段(例如String[]ids),我想到的解决方案是将origin对象中与target对象的同名字段值赋予target,再将待处理字段拆分后赋予target进行存储。首先想到的就......
  • java循环
    whilewhile(){}do{}while();for(;;){}增强for循环for(声明语句:表达式){}publicclasszqfor{  publicstaticvoidmain(String[]args){​    int[]a={10,20,30,40,50};    for(intx:a){      System.out.println(x);   ......
  • k8s 深入篇———— pod 实战[六]
    前言pod实战一下,主要是一些例子。正文例子一pod实例的选择:NodeSelector:是一个供用户将Pod与Node进行绑定的字段NodeName:一旦Pod的这个字段被赋值,Kubernetes项目就会被认为这个Pod已经经过了调度,调度的结果就是赋值的节点名字。所以,这个字段一般由调度器负责设......
  • [java学习] Spring的分页插件的使用
    概述:SSM集成常会使用到分页,Spring中提供了方便实用的分页插件  第一步:在Mybatis配置文件(SqlMapConfig.xml)中配置插件组件:<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTDConfig3.0//EN""http://myb......
  • Java 一维数组的使用
    Java一维数组的使用1.一维数组的定义在不知道数组内容可以直接使用下面的定义方法:int[]arr=newint[数组个数];或intarr[]=newint[数组个数];在知道数组内容可以使用如下:int[]arr={data1,data2,data.....};2.数组的传递数组的传递与其他基本类型的值传递不同,......
  • 55基于java的在线零食超市系统设计与实现
    本章节给大家带来一个基于java在线零食超市系统设计与实现,可适用于零食小吃,在线零食小吃超市,线上超市,线上零食商城,美食商城,美食超市,校园超市,零食资讯等等。引言随着社会的快速发展,计算机的影响是全面且深入的。人们生活水平的不断提高,日常生活中人们对交易平台方面的要求也在......
  • JAVA编程开发之 新蜂商城 --- 简介
    开源商城学习项目https://github.com/newbee-ltd/newbee-mallhttps://gitee.com/newbee-ltd/newbee-mallhttps://edu.csdn.net/course/detail/26258https://juejin.cn/book/6844733814074245133?suid=3808363978174302&source=pc新蜂商城虽然不能作为真实企业级项目,但是是优......
  • JavaScript对象
    JavaScript对象Object类型,我们也称为一个对象。是JavaScript中的引用数据类型它是一种复合值,它将很多值聚合到一起,可以通过名字访问这些值对象也可以看做是属性的无序集合,每个属性都是一个key/value对对象除了可以创建自有属性,还可以通过从一个名为原型的对象那里继承属性除......
  • Java注解介绍
    1.元注解Java定义了4个标准得meta-annotation,用于对注解作说明@Target:描述注解的使用范围,即注解可以用在什么地方(如类,方法、成员变量等)@Retention:表示注解的生命周期(SOURCE<CLASS<RUNTIME)(一般选择RUNTIME)@Document:说明注解是否被包含在javadoc中@Inherited:说......
  • 8. Java-AOP 面向切面编程
    专题使用汇总:Java-IDEAJava-Maven,依赖管理,私服https://www.cnblogs.com/chenshaojun2008/p/17493632.htmlJava-IOC&DIJava-Mybatis连接池,动态sqlhttps://www.cnblogs.com/chenshaojun2008/p/17496913.htmlJava-文件上传(本地和OSS)Java-登录校验JWT,过滤器,拦截器使用总结......