Java里的线程神器:ThreadLocal

news2024/9/24 15:26:04

今天我们要学习一种在JAVA线程中至关重要的类——ThreadLocal。

ThreadLocal是一个强大的JAVA类,它能实现线程局部变量的功能。通过ThreadLocal,每一个线程都可以拥有自己的一份变量副本,互相之间不会影响操作,真正做到数据隔离。尤其在多线程环境下,避免了线程间混乱的数据共享、避免出现竞争条件,使得编程更加优雅。

ThreadLocal通常用来解决线程安全性问题,比如在Web应用中,你可以把数据库连接、用户身份信息等放在ThreadLocal里,确保每个线程都能独自访问这些信息,互不干涉。除此之外,ThreadLocal还常用于实现线程封闭(Thread Confinement)的模式,将数据完全限制在单个线程内,极大地简化了并发编程的难度。

我个人工作经历里,确实用得比较少,可能因为我的工作与之关系不大。但认识这个类对于你的工作肯定大有裨益。我们都知道,当JAVA方法需要传参数时,如两个方法间进行交互,传出值是另一个方法的输入值,随着方法越来越多,我们就容易遇到问题。此时如果利用ThreadLocal,每个线程可以独立保存一个值,非常方便。

下面让我们不再空谈理论,直接开始实际操作吧。让我们先学习一下如何使用这个类。

这里我们不再特意新建一个项目来做这件事情,可以借鉴。我之前关于servlet的文章:Java技术中的经典之作:Servlet的实践运用,我们就在这个项目上做一下二次创作吧。

构建它

首先我们需要构造这样一个类

package com.masiyi.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
 * @Author masiyi
 * @Date 2023/12/18
 * @PackageName:com.masiyi.servlet
 * @ClassName: MyTheadLocal
 * @Description: TODO
 * @Version 1.0
 */
@WebServlet(urlPatterns = "/myThreadLocal")
public class MyThreadLocal extends HttpServlet {

    ThreadLocal threadLocal = new ThreadLocal<String>();
    /**
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        threadLocal.set("你好");
        method1();
    }

    private void method1() {
        System.out.println(threadLocal.get());
        method2();
    }

    private void method2() {
//        threadLocal.remove();
        System.out.println(threadLocal.get());
    }

    /**
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(threadLocal.get());
        super.doPost(req, resp);
    }
}

启动后,我们用到接口调用工具,比如apifox,然后我们去调用这个接口。

学习它

跑到这个方法里面以后,我们可以看到我们是用成员属性方法创建了一个成员变量,这个时候我们给这个成员变量设置一个值。这里需要了解的是,每个HTTP请求就是一条单独的线程。在这个线程中,无论我们怎么调方法,它都会在同一个线程中执行,除非我们新建了一个异步线程来完成其他操作。所以,我们可以看到我们现在所在线程的名字是"http-nio-8080-exec-1"@2,977。

在这里插入图片描述

上面提到过,这个成员变量只有当前线程可以访问。也就是说,在这个线程中设置了一个值后,其他线程无法获取这个变量,只能由当前线程访问。就像我们现在正在使用的method1方法,我们直接从get方法中获取它设定的值,结果显示的是"你好"。

在这里插入图片描述

但是,现在如果我们调用这个servlet的post方法,我们将发现它与我们刚刚的线程不同。

在这里插入图片描述

那么,自然地,它直接获取的值就是null。

在这里插入图片描述

现在我们再次调用get方法,这时我们会发现它的线程已经改变了,这就是我说的道理。每个请求都会有一个独立的线程。因此,当我们直接调用它的get方法时,就得到了null。
接下来,我们再调用method1方法。会发现我们在doget方法中已经设定的值,这次可以顺利获取了。

在这里插入图片描述

最后,在这个方法中我们将其remove掉。remove意味着所有的值都会被清空。所以,我们看到的值就是null。

在这里插入图片描述

通过这个类,我们就可以实现了,方法的输入和输出参数不用明确标明它们的具体值,我们可以通过这个类来传递值,而且这个类的成员变量是每个线程专用的,每个线程都是独立的。

利用它

ThreadLocal在工作中可以用来解决线程安全和上下文传递的问题。举个例子,假设我们有一个需要在多个方法中传递用户身份信息的场景,可以使用ThreadLocal来存储用户身份信息,以便在整个线程中访问。

public class UserContext {
    private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();

    public static void setUser(User user) {
        userThreadLocal.set(user);
    }

    public static User getUser() {
        return userThreadLocal.get();
    }

    public static void clear() {
        userThreadLocal.remove();
    }
}

public class UserService {
    public void processUserRequest() {
        User user = // 从请求中获取用户信息
        UserContext.setUser(user);
        // 执行业务逻辑
        // 在其他方法中可以通过UserContext.getUser()获取用户信息
        UserContext.clear();
    }
}

在上面的例子中,UserContext类使用ThreadLocal存储用户信息,而UserService类中的processUserRequest方法可以在整个线程中访问和传递用户信息,而不需要显式地将用户信息作为参数传递给每个方法。这样可以简化代码,避免在方法间传递上下文信息的复杂性。

查看它

让我们仔细看看这三个方法,set gat remove,它们各有什么特点,为什么能做到如此独特的操作?

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里捞出对应的map。如果映射存在,就把值藏进去;如果映射不存在,那就造一个新的出来,然后再把值藏进去。总之就是要保证每个线程都有自己专享的ThreadLocalMap,顺利实现了线程局部变量的存储和获取,避免了多线程环境下的数据共享和争抢问题。

具体来说,这段代码里最关键的操作就是通过ThreadLocalMap来实现线程局部变量的存储和访问,同时保证每个线程都能自由处理自己的变量副本。

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();
  }

这是ThreadLocal类里的get方法,用于从当前线程的ThreadLocalMap里把保存的值取出来。首先,它拿到当前线程的引用,然后尝试从当前线程的ThreadLocalMap里把对应的map找到。如果映射存在,就尝试从map里把值找出来,如果找到了就直接返回;如果找不到,就调用setInitialValue方法来初始化一个值并回传。

这个方法的作用就是确保每个线程都能取到自己专享的那份ThreadLocalMap里保存的值。如果当前线程的ThreadLocalMap里有对应的值,那就直接取回来;如果没有,那就可以通过setInitialValue方法初始化一个值,然后继续取回来。

remove方法

 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m!= null) {
       m.remove(this);
     }
 }

remove方法主要用于从当前线程的ThreadLocalMap中把跟当前ThreadLocal对象相关联的值给清理掉。首先,它拿到当前线程的引用,然后尝试从当前线程的ThreadLocalMap里找到对应的map。如果map存在,就调用map的remove方法把跟ThreadLocal对象相关联的值给清理掉。

这个方法主要是为了保证在不再需要用ThreadLocal对象保存的值时,能将其从当前线程的ThreadLocalMap中清理掉,避免内存泄露或者无谓的资源占用。

如果你去深入研究源码,就会发现这里面主要涉及三个类,ThreadLocalMap、Entry和ThreadLocal。它们的关系大致如下:

  • ThreadLocalMap是ThreadLocal类的一个子类,主要用来存储线程局部变量的值。每个线程都有自己专属的ThreadLocalMap实例,以此来存储该线程所需的局部变量值。
  • Entry是ThreadLocalMap里面的一个内置类,主要用来表示ThreadLocalMap里的项。每个Entry实例都包括一个ThreadLocal对象和相应的值,用于描绘线程局部变量的键值对。
  • ThreadLocal是一个线程局部变量,它提供了get、set和remove等方法,用于在当前线程里存取局部变量的值。每个ThreadLocal实例都跟当前线程的ThreadLocalMap里面的一个Entry相关联,以此来实现线程局部变量的存储和访问。

于是,ThreadLocalMap主要用来存储线程局部变量的值,Entry是用来描绘存储在ThreadLocalMap里面的键值对,而ThreadLocal就是线程局部变量的一种抽象表示,它跟ThreadLocalMap里面的Entry相绑定,使得线程局部变量的存储和访问得以实现。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1596013.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux 5.10 Pstore 学习之(二) 原理学习

目录 编译框架模块初始化pstore子系统ramoops模块初始化实例化注册回调数据结构 pstore_blk模块pstore_zone模块 测试扩展调试 编译框架 目标结构 linux_5.10/fs/pstore/ ├── blk.c ├── ftrace.c ├── inode.c // 核心1 ├── internal.h ├── Kconfig ├── …

音乐文件逆向破解

背景 网易云等在线音乐文件的加密源码都按照一定的规则加密&#xff0c;通过对音乐文件的源码分析转化&#xff0c;有望实现对加密文件的解密 实现内容 实现对加密音乐文件的解密 实现对无版权的音乐文件的转化 实现环境 010editor 010 Editor是一个专业的文本编辑器和十六…

IDEA pom.xml显示灰色并被划线

在使用 IDEA 进行开发的过程中&#xff0c;有时候会遇到 pom.xml 显示灰色并被划线的情况&#xff0c;如下图&#xff1a; 这一般是因为该文件被 Maven 忽略导致的&#xff0c;可以进行如下操作恢复&#xff1a; 设置保存后&#xff0c;可以看到 pom.xml 恢复了正常&#xff1a…

【UE5.1】使用MySQL and MariaDB Integration插件——(1)连接MySQL

效果 步骤 1. 在虚幻商城下载“MySQL and MariaDB Integration”插件 2. 购买安装后&#xff0c;我们将插件添加到一个新工程中&#xff0c;打开新工程可以看到已经添加了插件 3. 新建一个蓝图&#xff0c;选择父类为“MySQLDBConnectionActor” 这里命名为该蓝图为“BP_MySQL…

【菜狗学前端】npm i -g nodemon 遇到的下载卡住及运行权限问题解决记录

一、下载nodemon原因 nodemon作用&#xff1a;用node环境运行js文件时可以实时刷新运行出结果 (即修改js代码后不需再手动重新运行js文件) 二、下载卡住 reify:semver:timing reifyNode:node_modules/nodemon Completed 卡住位置&#xff1a;reify:semver: timing reifyNode…

【Java探索之旅】数组概念与初始化指南:动静结合

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java编程秘籍 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、初识数组1.1 为什么要有数组&#xff1f;1.2 数组的的概念 二、数组的创建及初始化…

【Node.js】Express学习笔记(黑马)

目录 初识 ExpressExpress 简介Express 的基本使用托管静态资源nodemon Express 路由路由的概念路由的使用 Express 中间件中间件的概念Express 中间件的初体验中间件的分类 初识 Express Express 简介 什么是 Express&#xff1f; 官方给出的概念&#xff1a;Express 是基于…

SpringMVC--获取请求参数 / 域对象共享数据

目录 1. SpringMVC 获取请求参数 1.1. 通过ServletAPI获取 1.2. 控制器方法形参获取 1.3. RequestParam 1.4. RequestHeader 1.5. CookieValue 1.6. 通过POJO获取请求参数 1.7. 解决获取请求参数的乱码问题 2. 域对象共享数据 2.1. 三大域对象 2.2. 准备工作 2.3. S…

海外短剧系统开发:引领全球短剧新潮流,打造跨文化娱乐新体验

随着全球化和互联网的快速发展&#xff0c;跨文化娱乐已经成为人们日常生活中不可或缺的一部分。海外短剧作为一种新颖、便捷的娱乐形式&#xff0c;正逐渐受到越来越多观众的喜爱。为了满足广大用户的需求&#xff0c;我们荣幸地推出全新的海外短剧系统开发方案&#xff0c;旨…

IDEA 找不到或无法加载主类

IDEA 中&#xff0c;有时候会遇到明明存在这个类&#xff0c;import 也没有报错&#xff0c;但编译时会报找不到或无法加载主类。 解决方法&#xff1a; 图像化操作 右侧 Maven > 根项目 > Lifecycle > clean > install 命令操作 mvn clean install

Java单例集合

Collection接口介绍 Collection 表示一组对象&#xff0c;它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。 Collection接口中定义的方法 方法说明boolean add(Object element)增加元素到容器中boolean remove(Object element)从容器中移除元素 boolean c…

Angular 使用DomSanitizer防范跨站脚本攻击

跨站脚本Cross-site scripting 简称XSS&#xff0c;是代码注入的一种&#xff0c;是一种网站应用程序的安全漏洞攻击。它允许恶意用户将代码注入到网页上&#xff0c;其他用户在使用网页时就会收到影响&#xff0c;这类攻击通常包含了HTML和用户端脚本语言&#xff08;JS&…

代码随想录算法训练营三刷day55 | 动态规划之子序列 392.判断子序列 115.不同的子序列

day55 392.判断子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组 115.不同的子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历…

【Linux学习】初识Linux指令(二)

文章标题 1.rm 指令2.man指令3.nano指令4.cp指令5.mv指令6.alias指令7. cat与8.echo指令 ⚶文章简介 ⚶本篇文章继上篇文章Linux指令讲解&#xff0c;本篇文章主要会涉及到的指令会有&#xff1a;rm指令与 *&#xff08;通配符&#xff09;的搭配使用&#xff0c;man指令&…

CRMEB 开源/标准版商城系统客服配置教程

管理后台/设置/系统设置/商城配置/客服端配置 有系统客服/拨打电话/跳转链接可选&#xff0c;系统客服为系统自带的客服系统&#xff0c;拨打电话为用户点击联系客服为拨打客服电话的方式&#xff0c;跳转链接为可以跳转自己开发的客服系统或者第三方的客服系统或者企业微信的…

文献学习-33-一个用于生成手术视频摘要的python库

VideoSum: A Python Library for Surgical Video Summarization Authors: Luis C. Garcia-Peraza-Herrera, Sebastien Ourselin, and Tom Vercauteren Source: https://arxiv.org/pdf/2303.10173.pdf 这篇文章主要关注的是如何通过视频摘要来简化和可视化手术视频&#xff0c…

mediapipe人体姿态检测(全方位探索手部、面部识别、姿势识别与物体检测及自拍分割技术)

引言 本文将聚焦于MediaPipe对人体姿态检测的全面支持&#xff0c;包括手部、面部识别、全身姿势识别、物体检测以及自拍分割五大关键技术。通过深入了解这些功能&#xff0c;读者将能更好地运用MediaPipe在各种应用中实现精准的人体动作捕捉与分析。 一、手部关键点检测 Me…

Web应用程序中的常见安全漏洞

大家好&#xff0c;我是咕噜铁蛋&#xff01;今天&#xff0c;我想和大家聊聊一个在我们日常开发中经常遇到的问题——Web应用程序中的安全漏洞。在这个数字化时代&#xff0c;Web应用几乎无处不在&#xff0c;它们不仅方便了我们的生活&#xff0c;也推动了社会的进步。然而&a…

python实现简单的车道线检测

描述 python实现简单的车道线检测&#xff0c;本文章将介绍两种简单的方法 颜色阈值区域掩模canny边缘检测霍夫变换 这两种方法都能实现简单的车道线检测demo&#xff0c;注意仅仅是demo 下面的图片是用到的测试图片 方法1&#xff1a;颜色阈值&#xff08;Color Selection…

李廉洋:4.15黄金,原油最新资讯,美盘走势分析及策略。

由于欧洲央行很可能先于美联储降息&#xff0c;美元走强。法国兴业银行分析师基特•朱克斯表示&#xff0c;市场“假设我们看到欧洲央行将在6月降息&#xff0c;但美联储不会”&#xff0c;这对美元有利。朱克斯表示&#xff0c;尽管在货币政策决定之前会公布一些相关数据&…