ThreadLocal

ThreadLocal详解 ThreadLocal详解都在里面了.. 阅读详情

ThreadLocal

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

适用场景

一个线程在一个类中需要执行不同的方法,同时这些方法都需要相同的入参,就可以定义一个上下文环境

📌只是演示使用ThreadLocal,实际上ThreadLocal不支持父子线程的上下文传递,以及多线程间的上下文传递,应使用它的子类InheritableThreadLocal便可解决父子线程的上下文传递这个问题,但是依旧不可多线程间上下文传递,应使用阿里的TransmittableThreadLocal这个类,其继承InheritableThreadLocal,借助该jar包对线程池的再一层封装,便可实现多线程间的上下文传递。

创建一个上下文

package com.strap.mydemo.thread;

/**
 * <p>定义一个上下文</p>
 *
 * @author strap
 * @since 2023/3/20 19:50
 */
public class UserContext {

    /**
     * 用户名
     */
    private final String userName;

    /**
     * 登录token
     */
    private final String token;

    /**
     * 过期时间
     */
    private final Long exp;

    private UserContext(String userName, String token, Long exp) {
        this.userName = userName;
        this.token = token;
        this.exp = exp;
    }

    public String getUserName() {
        return userName;
    }

    public String getToken() {
        return token;
    }

    public Long getExp() {
        return exp;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String userName;

        private String token;

        private Long exp;

        public Builder userName(String userName) {
            this.userName = userName;
            return this;
        }

        public Builder token(String token) {
            this.token = token;
            return this;
        }

        public Builder exp(Long exp) {
            this.exp = exp;
            return this;
        }

        public UserContext build() {
            return new UserContext(this.userName, this.token, this.exp);
        }

    }
}

定义ThreadLocal存储上下文

package com.strap.mydemo.thread;

import cn.hutool.core.lang.Opt;

import java.util.function.Function;

/**
 * <p>自定义上下文环境</p>
 *
 * @author strap
 * @since 2023/3/20 19:51
 */
public class UserContextHolder {

    private static final ThreadLocal<UserContext> THREAD_USER_CONTEXT = new ThreadLocal<>();

    public static UserContext getContext() {
        return THREAD_USER_CONTEXT.get();
    }

    public static void initContext(UserContext businessContext) {
        THREAD_USER_CONTEXT.set(businessContext);
    }

    public static <T> T field(Function<UserContext, T> getter) {
        return Opt.ofNullable(THREAD_USER_CONTEXT.get()).map(getter).get();
    }

    public static void clearContext() {
        THREAD_USER_CONTEXT.remove();
    }
}

测试

package com.strap.mydemo.interceptors;

import com.strap.mydemo.service.IUserDetailService;
import com.strap.mydemo.thread.UserContextHolder;
import lombok.extern.log4j.Log4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p></p>
 *
 * @author strap
 * @since 2023/3/15 14:03
 */
@Log4j
public class LoginInterceptor implements HandlerInterceptor {

    private final IUserDetailService userDetailService;

    public LoginInterceptor(IUserDetailService userDetailService) {
        this.userDetailService = userDetailService;
    }

    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, Object handler) throws Exception {
        return userDetailService.verify(request);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 每次处理完成,记得清空上下文环境
        UserContextHolder.clearContext();
    }

}

vertify方法

    @Override
    public boolean verify(HttpServletRequest request) throws Exception {
        String token = StrUtil.removeAll(request.getHeader(Header.AUTHORIZATION.getValue()), TOKEN_HEADER_PREFIX);
        BaseResultType.ILLEGAL_ARG.isEmpty(token, () -> "token cannot be empty");
        JWT jwt = JWT.of(token);
        Long exp = Convert.toLong(jwt.getPayload(MyDemoConstants.UserConstants.JWT_EXPIRE_KEY), 0L);
        BaseResultType.TOKEN_EXPIRE.isError(exp < System.currentTimeMillis(), () -> "token has expired");
        boolean verify = jwt.verify(JWTSignerUtil.hs256(getLoginSecretStr()));
        if (verify) {
            // 登录验证成功,初始化上下文
            UserContextHolder.initContext(
                    UserContext.builder()
                            .token(token)
                            .userName(String.valueOf(jwt.getPayload("userName")))
                            .exp(exp)
                            .build()
            );
        }
        BaseResultType.FAILED.isError(!verify, () -> "invalid token");
        return verify;
    }

controller中不同方法获取上下文

    @GetMapping(path = "/queryData")
    public String quueryData() throws Exception {
        methodA();
        methodB();
        return "查询数据";
    }

    public void methodA(){
        log.info(UserContextHolder.field(UserContext::getUserName));
    }

    public void methodB(){
        log.info(UserContextHolder.field(UserContext::getToken));
    }

InheritableThreadLocal

解决父子线程的上下文传递问题

    private static final InheritableThreadLocal<UserContext> THREAD_USER_CONTEXT = new InheritableThreadLocal<>();

TransmittableThreadLocal

解决引用线程池时的上下文传递问题

引入对应坐标依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.14.2</version>
        </dependency>

替换上下文中使用的ThreadLocal

    private static final TransmittableThreadLocal<UserContext> THREAD_USER_CONTEXT = new TransmittableThreadLocal<>();

对线程池封装

关键是要适用阿里的线程池工具对线程池进行封装TtlExecutors.getTtlExecutor(executor),才可以在线程池中取线程时也能实现上下文信息的传递

package com.strap.mydemo.config;

import com.alibaba.ttl.threadpool.TtlExecutors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * <p></p>
 *
 * @author strap
 * @since 2023/3/21 10:48
 */
@EnableAsync
@Configuration
public class ThreadConfig {

    public ThreadConfig() {
    }

    @Bean("demo-async-executor")
    public Executor AsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数,设置成1方便观察
        executor.setCorePoolSize(1);
        // 线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程,设置成1方便观察
        executor.setMaxPoolSize(1);
        // 缓存队列
        executor.setQueueCapacity(20);
        // 空闲时间,当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(200);
        // 异步方法内部线程名称
        executor.setThreadNamePrefix("demo-async-executor");

        // 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
        // 通常有以下四种策略:
        // ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
        // ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
        // ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
        // ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return TtlExecutors.getTtlExecutor(executor);
    }

}

测试

    @GetMapping(path = "/queryData")
    public String quueryData() throws Exception {
        methodA();
        iUserDetailService.methodC();
        methodB();
        return "查询数据";
    }

    public void methodA(){
        log.info(Thread.currentThread().getName() + "--" + UserContextHolder.field(UserContext::getUserName));
    }
    
    public void methodB(){
        log.info(Thread.currentThread().getName() + "--" + UserContextHolder.field(UserContext::getToken));
    }

接口实现类增加异步线程方法

    @Override
    @Async("demo-async-executor")
    public void methodC() throws Exception{
        log.info(Thread.currentThread().getName() + "--" + UserContextHolder.field(UserContext::getToken));
    }

结果

[com.strap.mydemo.controller.TestController]-https-jsse-nio-8443-exec-1–test
[com.strap.mydemo.controller.TestController]-https-jsse-nio-8443-exec-1–aa
[com.strap.mydemo.service.impl.UserDetailServiceImpl]-demo-async-executor1–aa

【ThreadLocal源码】深入浅出ThreadLocal ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候,都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态变量,使用它的目的是希望将状态(用户ID、事务ID等)与线程关联起来。1)、前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,例如当前登录用户放入ThreadLocal 中,避免每个方法一直传递过去,直接从ThreadLocal获取即可; 阅读详情

相关推荐

转录因子详细介绍(motif)

转录因子详细介绍(motif) TF: transcription factor转录因子TFBS: transcription factor binding site转录因子结合位点TFBS是序列内的location,TF特异结合在这里,这个site有这种特点 1 和一些参考相关的一个位置(开始,结束,strand),这些reference可以是染色体开始,geneTSS。也可以是一段sequence 2 A SITE可以是实验证实的(已知的),也可以是一些算法(预测的) 3 例子,下面这个图是酵母

wangprince2017 3万+

ThreadLocal 实战应用

在 Spring 项目中 Dao 层中装配的 Connection 肯定是线程安全的,其解决方案就是采用 ThreadLocal 方法,当每个请求线程使用 Connection 的时候, 都会从 ThreadLocal 获取一次,如果为 null,说明没有进行过数据库连接,连接后存入 ThreadLocal 中,如此一来,每一个请求线程都保存有一份 自己的 Connection。每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。

Stone.鱼儿的博客 1480

线阵相机标定必看:如何用Halcon实现亚毫米级精度测量(以HC050标定板为例)

本文以HC050标定板为例,详细阐述了如何利用Halcon实现线阵相机的高精度标定,以达到亚毫米级测量。文章深入剖析了线阵相机标定的核心原理、面临的独特挑战,并提供了从硬件搭建、图像采集到Halcon软件实操的完整路径,重点解析了运动参数设置、精度验证及常见问题排错,为精密测量应用提供系统解决方案。

weixin_29213655的博客 313

对ThreadLocal的理解【源码分析+应用举例】

ThreadLocal可以使每个线程保存自己的一些私有数据,起到线程隔离的作用。打个比方,可以将其比喻成大型超市里的公共储物柜,每个人都可以使用,但是储物柜又分为好多小箱子来保证每个顾客有属于自己的存储空间,只能存取自己的物品。

Mr_Liu946的博客 1480

点餐系统数据库设计--SQL Server

友情链接 1、学生成绩管理系统数据库设计–MySQL 学生成绩管理系统数据库设计–MySQL 2、医疗信息管理系统数据库–MySQL 医疗信息管理系统数据库–MySQL 3、邮件管理数据库设计–MySQL 邮件管理数据库设计–MySQL 4、SQL Server医疗信息管理系统数据库【英文版-源码】–(Medical Management System Database) SQL Server医疗信息管理系统数据库【英文版-源码】–(Medical Management System Database) 5

若如初见 8万+

PT静态时序分析实战:从基础命令到高级报告解析

本文详细介绍了PT(PrimeTime)静态时序分析(STA)的实战技巧,从基础命令如pt_shell和report_timing的使用,到高级报告解析与问题排查。通过丰富的实例和脚本示例,帮助工程师快速掌握时序分析的核心技能,提升芯片设计验证效率。

weixin_29289821的博客 955

huTool--工具类常用方法

转载链接 https://www.jianshu.com/p/fa5ef558b286 官方文档 https://www.hutool.cn/

liucy007的博客 733

PostgreSQL高可用之主备复制

多个PostgreSQL可协同工作,如果主库发生宕机备库将迅速进行接管并对外提供服务以实现高可用。本文将从主备复制、主备切换、故障转移等角度进行实际测试演练。第一章主备复制是基础,通过主备复制可以得到一个或多个相同的实例。在主备复制的基础上通过第二章主备切换介绍了如何手工将主库与备库互换角色。在第三章中,采用了Keepalived进行实时监测,在主库宕机后,使用主备切换中提到的命令使备库升级为主库并对外提供服务,以达到PostgreSQL高可用的目标。

asdasdsaff的博客 1348

用ThreadLocal作为memcache的事务解决方案遇到的问题

ThreadLocal 是java官方提供的一套基于本地变量的解决线程安全问题的解决方案,他是对于每一个线程提供一个只有本线程可见的变量副本,存放于以本线程对象为key的map中去。ThreadLocal接口简介:   ThreadLocal的接口方法   ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:   void set(Object value)   设置当前线...

weixin_30518397的博客 154

uniapp(全端兼容) - 最新详细实现接入腾讯视频播放器功能插件,uniapp全平台端(小程序/H5网页/App/nvu)调用引入并使用腾讯视频播放组件完整全流程(详细示例源码,一键复制开箱即用)

uniapp腾讯视频,uniapp微信小程序腾讯视频,uniapp小程序接入腾讯视频详细示例教程源码,uniapp怎么在微信小程序平台使用腾讯视频插件,在uni-app中使用腾讯视频插件播放视频,uniapp调用腾讯视频,uniapp引入腾讯视频插件,uniapp小程序中引入腾讯视频无法播放,uniapp微信小程序平台引入腾讯视频播放不了黑屏,在uniapp开发的微信小程序中使用腾讯视频,uniapp如何引入腾讯视频插件播放视频,uni-app小程序平台引入腾讯视频插件播放腾讯视频,uniapp 小程序接入

✅ 精选推荐文章 1万+

手把手教你学Simulink--基于Simulink的风力发电系统用双馈电机控制仿真建模

特点高效节能:在广泛的风速范围内保持高效的能量转换。独立控制有功和无功功率:通过控制转子电流实现对有功和无功功率的独立调节。较低的成本:相比直接驱动或全功率变流器方案,成本较低。挑战控制复杂度较高:需要复杂的控制算法来实现有功和无功功率的解耦控制。对电网波动敏感:需要良好的稳定性控制策略以应对电网电压波动。利用Simscape Electrical中的模块构建DFIG的物理模型,配置参数如电阻、电感、极对数等。matlab深色版本% 添加DFIG电机模块。

MHD0815的博客 349

分析Threadlocal内部实现原理,并解决Threadlocal的ThreadLocalMap的hash冲突与内存泄露

前言 ThreadLocal 的经典使用场景是数据库连接、session 管理、多线程等…… 比如在Spring中,发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都不同程度使用了ThreadLocal 。 Spring中绝大部分Bean,都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean,就能够以singleton的方式,在多线程中正常工作。 知道Threadlocal怎么用,但是不知道为什么要这样用?底层原理是.

阿啄debugIT 2212

2026年3月GESP真题及题解(C++七级):物流网络

2026年3月GESP真题及题解(C++七级):物流网络

王老师青少年编程 1126

ThreadLocal原理、ThreadLocal的使用、ThreadLocal内存泄漏、ThreadLocal的坑

文章目录背景说明一:ThreadLocal的原理1.1 什么是ThreadLocal变量1.2 ThreadLocal实现原理1.3 ThreadLocalMap实现原理1.4 ThreadLocalMap结构图二:ThreadLocal的使用2.1 创建ThreadLocal工具类2.2 在拦截器/过滤器中通过用户token获取登录信息,并与当前线程绑定2.3 业务处理时获取用户信息2.4 当前请求结束时,销毁ThreadLocal变量三:ThreadLocal的坑、ThreadLocal内存泄漏3.1

weixin_49114503的博客 8911

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

muduo之ThreadLocal

ThreadLocal用来存储线程私有数据的类。 // Use of this source code is governed by a BSD-style license // that can be found in the License file. // // Author: Shuo Chen (chenshuo at chenshuo dot com) #ifn...

1万+

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

理解ThreadLocal

ThreadLocal是什么早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为T

Java之道 27万+

ThreadLocal对象

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这 个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值,也就是是说可以让线程与保存值的关联起来,让每个线程都有一个自己的变量,实现线程安全。 可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。 在代码清单4-15所示的例子中,构建了一个常用的Profiler类,它具有begin()和end()两个 方法,而e

tian830937的专栏 720

threadLocal原理

一.ThreadLocal的作用   ThreadLocal提供的实际上就是一种线程隔离,不同的线程的变量归属不同的线程,线程之间不能想问访问各自的变量,当线程生命周期结束的时候,线程会被销毁。 二.ThreadLocal的原理 Thread类中有一个成员变量threadLocals: 这个ThreadLocalMap 是ThreadLocal类中一个静态内部类; ThreadLocal类中最主要的就是set 方法get方法; set方法 这个getMap(t); 传入一个Thr.

sam19911的博客 5531

ThreadLocal 详解

1. ThreadLocal 是用来干什么的 ThreadLocal 是一个以 ThreadLocal 对象为键、任意对象为值的存储结构,这个结构被附带在线程上,也就是说一个线程可以根据一个 ThreadLocal 对象查询到一个绑定在这个线程上的值。 2. ThreadLocal 示例 代码如下: public class TestThreadLocal { private static final ThreadLocal<String> STRING_THREAD_LOCAL = n

明月燃雨 7813

线程副本之ThreadLocal

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到一个绑定到这个线程上的值。 可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法 获取到原先设置的值。 **代码示例:**构建了一个常用的Profiler类,它具 有begin()和end()两个方法,而end()方法返回从begin()方法调用开始到 end()方法被调用时的时间差,单位是毫秒。 publi

weixin_43902350的博客 316

详解ThreadLocal

Thread存储线程独有的变量(存储多个变量(值)时,可以创建多个ThreadLocal用来存储),以供在整个线程存活过程中取用。这种能力由ThreadLocal中的ThreadLocalMap实现。Thread在存储自己独有变量时,ThreadLocal实例在该Thread环境内使用set方法实现变量(值)存储。(继续探究原理)实际是静态内部类ThreadLocalMap将该变量(值)以key=ThreadLocal实例,value=变量(值)的键值对对象Entry形式存储到了ThreadLocalMap

文化园 4543

利用threadLocal实现对执行时间的统计

import java.util.Properties; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.*; /* * 利用 threadLocal 实现 对执行时间的统计 * */ public class Profiler { private static final ThreadLocal<Long> TIME_THREADLOCAL = new Thread.

yt438936731的博客 1223
下一篇: 设计模式随记
strap
博客等级 码龄3年 60粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值