全面理解ThreadLocal(详细简单)(一)

简介: 全面理解ThreadLocal(详细简单)

一、ThreadLocal简介

从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。 这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程的变量。ThreadLocal的实例通常来说都是private static 类型的,用于关联线程和线程上下文。

我们可以得知ThreadLocal的作用是:提供线程内部的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。

ThreadLocal功能总结:

  • 线程并发:在多线程并发的场景下;
  • 传递数据:可以通过ThreadLocal在同一个线程,不同组件中传递公共变量;
  • 线程隔离:每个线程的变量都是独立的,不会互相影响;

ThreadLocal的应用场景:

  • 前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,例如当前登录用户放入ThreadLocal 中,避免每个方法一直传递过去,直接从ThreadLocal获取即可;
  • 将数据库Connection连接对象存入ThreadLocal中,保证了不同的线程使用线程相关的Connection,而不会使用其他线程的Connection;

二、ThreadLocal的基本使用

在使用之前,我们先来看看ThreadLocal的一些常用方法:

方法声明 作用
ThreadLocal() 创建ThreadLocal对象
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量

我们来看下面这个示例,感受一下ThreadLocal线程隔离的特点:


package com.wsh.mybatis.mybatisdemo.entity;
public class ThreadLocalDemo {
    private static ThreadLocal threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(() -> {
            //设置t1线程中本地变量的值
            threadLocal.set("t1线程");
            //获取t1线程中本地变量的值
            System.out.println("t1线程局部变量的value : " + threadLocal.get());
        }, "t1").start();
        new Thread(() -> {
            //设置t2线程中本地变量的值
            threadLocal.set("t2线程");
            //获取t1线程中本地变量的值
            System.out.println("t2线程局部变量的value : " + threadLocal.get());
        }, "t2").start();
    }
}

下面是运行后的结果:


t1线程局部变量的value : t1线程
t2线程局部变量的value : t2线程

可以看到,每个线程只会拿到属于它们的线程局部变量副本信息,不会出现t1线程拿到t2线程的变量value,这就是ThreadLocal的线程隔离性。

三、ThreadLocal与Synchronized的区别

在前面的案例中,其实我们也可以使用synchronized关键字加锁来达到同样的线程隔离的效果,虽然ThreadLocal与synchronized关键字都用于处理多线程并发访问变量的问题,不过两者处理问题的角度和思路不同。

区别 Synchronized ThreadLocal
原理 同步机制采用“以时间换空间”的方式,只提供了一份变量,让不同的线程排队访问 ThreadLocal采用“以空间换时间”的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰
侧重点 多个线程之间访问资源的同步 多线程中让每个线程之间的数据相互隔离

四、ThreadLocal的内部结构

通过以上的学习,我们对ThreadLocal的作用有了一定的认识。现在我们一起来看一下ThreadLocal的内部结构,有利于我们理解ThreadLocal实现线程数据隔离的原理。

JDK最早期的ThreadLocal的设计:每个ThreadLocal都创建一个ThreadLocalMap,然后用线程thread对象作为Map的key,要存储的局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。大概如下图所示:

image.png

在JDK1.8中,JDK后面优化了设计方案:每个线程维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。

具体的过程如下:

  • 每个线程Thread内部都有一个ThreadLocalMap对象;
  • ThreadLocalMap里面存储ThreadLocal对象(key)和线程的变量副本(value);
  • 线程内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值;
  • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰;

大体结构图如下所示:

image.png

比较上面两种方案,JDK1.8优化后的好处主要有下面两点:

(1)、每个ThreadLocalMap中存储的Entry数量变少;(因为1.8之前的设计,thread线程越多,ThreadLocalMap中的Entry数量自然就越多;而1.8之后,Entry的数量是由ThreadLocal的数量来决定的);

(2)、当Thread线程销毁的时候,ThreadLocalMap也会随之销毁,减少内存的使用;

五、ThreadLocal的核心方法源码

基于ThreadLocal的内部结构,我们继续分析ThreadLocal的核心方法源码,更加深入的了解其操作原理。除了构造方法外,ThreadLocal对外暴露的方法主要有下面四个:

方法声明 描述
protected T initialValue() 返回当前线程局部变量的初始值
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量

set方法:设置当前线程对应的ThreadLocal的值


public void set(T value) {
    //获取当前线程对象
    Thread t = Thread.currentThread();
    //获取当前线程中维护的ThreadLocalMap对象
    //getMap(t)实际上就是获取当前线程对象自己的成员变量threadLocals
    ThreadLocalMap map = getMap(t);
    //判断ThreadLocalMap是否存在
    if (map != null)
         //如果ThreadLocalMap存在的话,调用map.set设置实体entry
        //key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
        map.set(this, value);
    else
        //当前线程thread不存在ThreadLocalMap对象,那么调用createMap方法创建一个ThreadLocalMap对象
        //t.threadLocals:将当前ThreadLocal对象和value(线程变量的副本)作为第一个entry, 存放至ThreadLocalMap对象中
        createMap(t, value);
}
//获取当前线程thread对应维护中的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//初始化一个ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
    //java.lang.Thread#threadLocals
    //ThreadLocal.ThreadLocalMap threadLocals = null;
    //并设置到当前线程thread的属性threadLocals中
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get()方法:获取当前线程的局部变量的副本


public T get() {
    //获取当前线程对象
    Thread t = Thread.currentThread();
    //获取当前线程维护的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    //如果ThreadLocalMap对象存在
    if (map != null) {
        //以当前threadLocal为key,调用getEntry方法获取对应的存储实体entry
        ThreadLocalMap.Entry e = map.getEntry(this);
         //判断entry对象是否为空
         if (e != null) {
            @SuppressWarnings("unchecked")
            //获取存储实体entry对应的value值
            //即为我们想要的当前线程对应此ThreadLocal的值
            T result = (T)e.value;
            return result;
        }
    }
    //初始化:有两种情况会执行下面代码:
    //1. map不存在,表示当前线程没有维护的ThreadLocalMap对象;
    //2. map存在,但是没有与当前ThreadLocal管理的Entry对象;
    return setInitialValue();
}
//初始化
private T setInitialValue() {
    //调用initialValue()获取初始化值
    //可以被子类重写,如果没有重写默认返回null
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取此线程对象中维护的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    //如果ThreadLocalMap不为空
    if (map != null)
        //设置value初始值为initialValue()方法返回的值
        map.set(this, value);
    else
        //如果ThreadLocalMap为空,创建一个ThreadLocalMap对象
        //t.threadLocals:将当前ThreadLocal对象和value(线程变量的副本)作为第一个entry存放至ThreadLocalMap对象中
        createMap(t, value);
    return value;
}

remove():移除当前线程局部变量的副本


public void remove() {
    //获取到当前线程的ThreadLocalMap对象
    ThreadLocalMap m = getMap(Thread.currentThread());
    //如果ThreadLocalMap对象不为空
    if (m != null)
        //以当前ThreadLocal对象作为key从ThreadLocalMap对象中移除对应的实体Entry对象
        m.remove(this);
}

initialValue():设置当前线程局部变量的初始值


//调用initialValue()获取初始化值
//可以被子类重写,如果没有重写默认返回null
protected T initialValue() {
    return null;
}

全面理解ThreadLocal(详细简单)(二)https://developer.aliyun.com/article/1393275

相关文章
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
1045 9
|
4月前
|
人工智能 测试技术 芯片
AMD Ryzen AI Max+ 395四机并联:大语言模型集群推理深度测试
本文介绍了使用四块Framework主板构建AI推理集群的过程,并基于AMD Ryzen AI Max+ 395处理器进行大语言模型推理性能测试,重点评估其并行推理能力及集群表现。
330 0
AMD Ryzen AI Max+ 395四机并联:大语言模型集群推理深度测试
|
负载均衡 网络协议 Dubbo
微服务架构 | 3. 注册中心与服务发现
注册中心用来集中管理微服务,实现服务的注册,发现,检查等功能;
3677 2
微服务架构 | 3. 注册中心与服务发现
|
负载均衡 应用服务中间件 API
Nginx配置文件详解Nginx负载均衡Nginx静态配置Nginx反向代理
Nginx配置文件详解Nginx负载均衡Nginx静态配置Nginx反向代理
355 4
ly~
|
开发框架 小程序 前端开发
抖音小程序的开发难度大吗?
抖音小程序的开发难度因人而异,主要取决于开发者经验、技能及功能需求。技术上需掌握前端技术及抖音开发框架,了解平台生态与规则;设计上需符合用户审美和习惯,具备创新性和实用性。此外,严格的审核标准和激烈的市场竞争增加了开发难度,开发者需制定有效推广策略并持续优化小程序以保持竞争力。
ly~
473 4
|
10月前
|
安全 搜索推荐 SEO
个人网站制作的步骤及流程
本文为个人网站制作流程指南,分为六步:明确需求、选择域名托管、搭建建站工具、完善素材内容、测试优化及发布维护。推荐使用PageAdmin CMS,拥有丰富的模板和编辑器,易上手且功能强大。
1437 1
Server-Sent Events 和 WebSocket 之间有什么区别
Server-Sent Events (SSE) 和 WebSocket 分别代表单向和双向通信机制。SSE,基于 HTTP,仅允许服务器向客户端发送事件流;而 WebSocket 是双向实时通信协议,支持客户端与服务器的双向交互。SSE适合低实时性场景,依赖长轮询或流传输;WebSocket 提供更低延迟,适用于高实时性应用。两者在现代浏览器中普遍被支持,但旧版浏览器或特定网络环境可能影响兼容性。选择哪种机制取决于实际需求,如通信方向、实时性要求及目标浏览器支持。
|
11月前
|
网络协议 Java Nacos
SpringCloudAlibaba-Seata2.0.0与Nacos2.2.1
检查 Nacos 控制台,确认 Seata 服务器和 Spring Boot 应用已成功注册。 - 通过执行全局事务验证 Seata 的分布式事务管理功能。
1191 31
|
消息中间件 数据可视化 Java
SpringBoot3集成Kafka
SpringBoot3集成KafkaKafka是一个开源的分布式事件流平台,常被用于高性能数据管道、流分析、数据集成和关键任务应用,基于Zookeeper协调的处理平台,也是一种消息系统,具有更好的吞吐量、内置分区、复制和容错。
1174 0