【多线程与高并发之ThreadLocal】——ThreadLocal简介

2024-08-26 00:48

本文主要是介绍【多线程与高并发之ThreadLocal】——ThreadLocal简介,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、ThreadLocal是什么?
  • 二、ThreadLocal的使用场景?
    • 1.线程内数据共享
    • 2.线程间数据隔离
  • 总结


前言

Java多线程是Java多任务执行的基础,那当一个任务会调用多个方法时,我们如何在一个线程内传递状态呢?


一、ThreadLocal是什么?

顾名思义,ThreadLocal是本地线程变量,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,线程内数据共享,线程间数据隔离。

二、ThreadLocal的使用场景?

ThreadLocal的使用场景主要是根据它的特性来的,也就是线程内数据共享,线程间数据隔离。

1.线程内数据共享

1、参数隐式传递
2、请求调用链中信息存储 - 例如(线程调用方法的方法,耗时,错误等信息)
3、……
下面是一个简单示例:
例如,小编目前正在做的一个项目,有一个计算排行榜的功能,有如下几个步骤:

1、从redis中获取用户各个业务线的基础数据信息(转入、转出、账户权益等)2、对用户的各个业务线数据进行汇总;
3、按照盈利计算公式进行盈利计算。

在这个方法中都需要传递用户这个对象,我们在不使用ThreadLocal时的写法如下,将username传入(使用线程池):

public static void main(String[] args) throws Exception {ExecutorService es = Executors.newFixedThreadPool(3);String[] users = new String[] { "Bill", "Cindy", "Alice"};for (String user : users) {es.submit(new Task(user));}es.awaitTermination(3, TimeUnit.SECONDS);es.shutdown();
}
class Task implements Runnable {final String userName;public Task(String userName) {this.userName = userName;}@Overridepublic void run() {new Task1().process(userName);new Task2().process(userName);new Task3().process(userName);}
}
class Task1 {public void process(String userName) {try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.printf("[%s] get %s basic data from redis...\n", Thread.currentThread().getName(), userName);}
}class Task2 {public void process(String userName) {try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.printf("[%s] collect %s data has done.\n", Thread.currentThread().getName(), userName);}
}class Task3 {public void process(String userName) {try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.printf("[%s] calc %s profit has done.\n", Thread.currentThread().getName(),userName);}
}

这种在一个线程中,将一个对象传递给若干方法,我们通常称之为上下文(Context),它是一种状态,可以是用户身份、任务信息等。
频繁的给每个方法添加上下文非常麻烦,而且还占用内存空间。于是,Java就提供了一个ThreadLocal机制,可以在一个线程中传递同一个对象,这个对象在线程间是共享的。
现在我们对以上的代码进行改造:
我们定义一个上下文类UserContext,这个类中,定义了一个userThreadLocal变量用来存储用户数据,一个set方法和一个get方法分别调用了ThreadLocal的set()和get(),用来设置和获取ThreadLocal中存储的变量,另外,这个类实现了AutoCloseable 类,重写了close()方法,任务执行完毕后,可以自动关闭ThreadLocal(后面会讲到,为什么要关闭ThreadLocal)。当任务在执行时,并不需要传递user对象,而是直接从ThreadLocal中获取。
代码如下:

class UserContext implements AutoCloseable {private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();public UserContext(String name) {userThreadLocal.set(name);System.out.printf("[%s] init user %s...\n", Thread.currentThread().getName(), UserContext.getCurrentUser());}public static String getCurrentUser() {return userThreadLocal.get();}@Overridepublic void close() {System.out.printf("[%s] remove user %s...\n", Thread.currentThread().getName(),UserContext.getCurrentUser());userThreadLocal.remove();}
}
class Task implements Runnable {final String userName;public Task(String userName) {this.userName = userName;}@Overridepublic void run() {try (var ctx = new UserContext(this.userName)) {new Task1().process();new Task2().process();new Task3().process();}}
}class Task1 {public void process() {try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.printf("[%s] get %s basic data from redis...\n", Thread.currentThread().getName(), UserContext.getCurrentUser());}
}class Task2 {public void process() {try {Thread.sleep(10);} catch (InterruptedException e) {}System.out.printf("[%s]  collect %s data has done.\n", Thread.currentThread().getName(), UserContext.getCurrentUser());}

以上,就是ThreadLocal线程内数据共享的简单使用场景。
再讲一个框架上的使用场景(参数隐式传递):
Spring的事务管理器通过AOP切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从DataSource中获取一个连接对象,通过一定的包装后将其保存在ThreadLocal中。而且Spring也将DataSource进行了包装,重写了当中的getConnection()方法,或者说该方法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同一个。
为什么要放在ThreadLocal里面呢?由于Spring在AOP后并不能向应用程序传递参数。应用程序的每一个业务代码是事先定义好的,Spring并不会要求在业务代码的入口参数中必须编写Connection的入口参数。此时Spring选择了ThreadLocal,通过它保证连接对象始终在线程内部,不论什么时候都能拿到,此时Spring很清楚什么时候回收这个连接,也就是很清楚什么时候从ThreadLocal中删除这个元素。

2.线程间数据隔离

为了解决多线程并发问题、数据库连接、Session 管理等而设计的,如下参考资料,作者已经写的很详细了,我就不过多赘述了。

ThreadLocal 那点事儿
ThreadLocal 那点事儿(续集)

总结

ThreadLocal到底怎么应用,还是要根据我们的具体业务场景来分析。本篇博客介绍了ThreadLocal的一些基础知识,下篇博客让我们来分析下ThreadLocal的源码,了解下ThreadLocal是如何工作的。

这篇关于【多线程与高并发之ThreadLocal】——ThreadLocal简介的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1107073

相关文章

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

Java Stream 并行流简介、使用与注意事项小结

《JavaStream并行流简介、使用与注意事项小结》Java8并行流基于StreamAPI,利用多核CPU提升计算密集型任务效率,但需注意线程安全、顺序不确定及线程池管理,可通过自定义线程池与C... 目录1. 并行流简介​特点:​2. 并行流的简单使用​示例:并行流的基本使用​3. 配合自定义线程池​示

PostgreSQL简介及实战应用

《PostgreSQL简介及实战应用》PostgreSQL是一种功能强大的开源关系型数据库管理系统,以其稳定性、高性能、扩展性和复杂查询能力在众多项目中得到广泛应用,本文将从基础概念讲起,逐步深入到高... 目录前言1. PostgreSQL基础1.1 PostgreSQL简介1.2 基础语法1.3 数据库

Python多线程实现大文件快速下载的代码实现

《Python多线程实现大文件快速下载的代码实现》在互联网时代,文件下载是日常操作之一,尤其是大文件,然而,网络条件不稳定或带宽有限时,下载速度会变得很慢,本文将介绍如何使用Python实现多线程下载... 目录引言一、多线程下载原理二、python实现多线程下载代码说明:三、实战案例四、注意事项五、总结引

Spring Security 前后端分离场景下的会话并发管理

《SpringSecurity前后端分离场景下的会话并发管理》本文介绍了在前后端分离架构下实现SpringSecurity会话并发管理的问题,传统Web开发中只需简单配置sessionManage... 目录背景分析传统 web 开发中的 sessionManagement 入口ConcurrentSess

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.

Python库 Django 的简介、安装、用法入门教程

《Python库Django的简介、安装、用法入门教程》Django是Python最流行的Web框架之一,它帮助开发者快速、高效地构建功能强大的Web应用程序,接下来我们将从简介、安装到用法详解,... 目录一、Django 简介 二、Django 的安装教程 1. 创建虚拟环境2. 安装Django三、创

MySQL中处理数据的并发一致性的实现示例

《MySQL中处理数据的并发一致性的实现示例》在MySQL中处理数据的并发一致性是确保多个用户或应用程序同时访问和修改数据库时,不会导致数据冲突、数据丢失或数据不一致,MySQL通过事务和锁机制来管理... 目录一、事务(Transactions)1. 事务控制语句二、锁(Locks)1. 锁类型2. 锁粒