【多线程与高并发之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

相关文章

ASIO网络调试助手之一:简介

多年前,写过几篇《Boost.Asio C++网络编程》的学习文章,一直没机会实践。最近项目中用到了Asio,于是抽空写了个网络调试助手。 开发环境: Win10 Qt5.12.6 + Asio(standalone) + spdlog 支持协议: UDP + TCP Client + TCP Server 独立的Asio(http://www.think-async.com)只包含了头文件,不依

高并发环境中保持幂等性

在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法: 唯一标识符: 请求唯一标识:在每次请求中引入唯一标识符(如 UUID 或者生成的唯一 ID),在处理请求时,系统可以检查这个标识符是否已经处理过,如果是,则忽略重复请求。幂等键(Idempotency Key):客户端在每次

业务协同平台--简介

一、使用场景         1.多个系统统一在业务协同平台定义协同策略,由业务协同平台代替人工完成一系列的单据录入         2.同时业务协同平台将执行任务推送给pda、pad等执行终端,通知各人员、设备进行作业执行         3.作业过程中,可设置完成时间预警、作业节点通知,时刻了解作业进程         4.做完再给你做过程分析,给出优化建议         就问你这一套下

容器编排平台Kubernetes简介

目录 什么是K8s 为什么需要K8s 什么是容器(Contianer) K8s能做什么? K8s的架构原理  控制平面(Control plane)         kube-apiserver         etcd         kube-scheduler         kube-controller-manager         cloud-controlle

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

【Tools】AutoML简介

摇来摇去摇碎点点的金黄 伸手牵来一片梦的霞光 南方的小巷推开多情的门窗 年轻和我们歌唱 摇来摇去摇着温柔的阳光 轻轻托起一件梦的衣裳 古老的都市每天都改变模样                      🎵 方芳《摇太阳》 AutoML(自动机器学习)是一种使用机器学习技术来自动化机器学习任务的方法。在大模型中的AutoML是指在大型数据集上使用自动化机器学习技术进行模型训练和优化。

Java并发编程之——BlockingQueue(队列)

一、什么是BlockingQueue BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种: 1. 当队列满了的时候进行入队列操作2. 当队列空了的时候进行出队列操作123 因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度

Java 多线程的基本方式

Java 多线程的基本方式 基础实现两种方式: 通过实现Callable 接口方式(可得到返回值):

java线程深度解析(五)——并发模型(生产者-消费者)

http://blog.csdn.net/Daybreak1209/article/details/51378055 三、生产者-消费者模式     在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之