真实项目中 ThreadLocal 的妙用

一、什么是 ThreadLocal

ThreadLocal 提供了线程的局部变量,每个线程都可以通过 set() 和 get() 来对这个局部变量进行操作,但不会和其他线程的局部变量冲突,实现了线程间的据隔离。

简单讲:一个获取用户的请求线程 A,如果向 ThreadLocal 填充变量 AValue(只能被线程 A 操作),该变量对其他获取用户的请求线程 B、C...是隔离的.

最简单的使用方式:

类似一次 HTTP 请求线程中,利用 ThreadLocal 存储 Cookie 对象,进行状态管理。set Cookie:

private ThreadLocal httpThreadLocal = new ThreadLocal();

httpThreadLocal.set(“Cookie: sid=13420771402233”)

上面存储格式是 String ,实际场景存储的是具体的对象。在这次 HTTP 请求过程中,任何时候都可以获取 Cookie 。获取方式很简单 get Cookie:

String cookieValue = (String) httpThreadLocal.get();

Thread 与 ThreadLocal 对象引用关系图

二、你熟悉的场景

2.1 数据库连接池

比如一次请求线程进来,业务 Dao 需要更新 user 表和 user-detail 表。如果是 new 出两个数据库 Connection ,分别不同的 Connection 操作 user 表和 user-detail 表,就无法保证事务。那么数据库连接池是如何保证的?

答案是:利用 ThreadLocal 存储唯一 Connection 对象。每次请求线程,pool.getConnection 获取连接的时候都会这样操作:

  • 会从 ThreadLocal 获取 Connection 对象。如果有,则保证了后面多个数据库操作共用同一个 Connection ,从而保证了事务。
  • 如果没有,往 ThreadLocal 新增Connection 对象,并返回到线程
错误的做法
public class XXXService {

    private Connection conn;
}

因为 conn 是线程不安全的。这样会导致多个请求公用一个连接。请求量很大的情况下,延迟各种。你懂。

因此,使用 ThreadLocal 保证每个请求线程的 Connection 是唯一的。即每个线程有自己的连接。

继续讲到 Spring 框架,在事务开始时,会给当前线程一个Jdbc Connection,在整个事务过程,都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离

2.2 HTTP Cookie

比如你访问百度、我访问百度,会有不同 Cookie 。而且你不能访问我的 Cookie,我也不能。顾名思义,使用 ThreadLocal 保证每个 HTTP 请求线程的 Cookie 是唯一的。

Cookie 这样才能做 Session 等状态管理。

三、实战场景

总结一下就是:ThreadLocal 可以让同一个线程中上下文之间数据共享

在上面章节 二、你熟悉的场景 其实介绍了很多现有场景。那么我这边具体的实战场景是什么?

简单的例子:

适用满足这两个条件的场景:1.每个线程独有的一些信息,2.这些信息又会在多个方法或类中用到。

  1. 一个请求线程,里面有两个异步小线程,各有一个方法。分别处理 A 或 B 业务
  2. 一种方法是传递不可变的入参
  3. 另一种就是 ThreadLocal,放在 ThreadLocal 的入参,会被各个方法共享。而且多个请求线程互不影响
复杂的例子:

一次发货操作:会根据入参,进行组件化、流程编排话。那么入参会被各个地方用到,而且有些流程组件是异步的(类似 new thread 操作的)。这时候可以定一个 XXContext 上下文:

public class XXContext {

    private static ThreadLocal<Map<Class<?>, Object>> context = new InheritableThreadLocal<>();

    /**
     * 把参数设置到上下文的Map中
     */
    public static void put(Object obj) {
        Map<Class<?>, Object> map = context.get();
        if (map == null) {
            map = new HashMap<>();
            context.set(map);
        }
        if (obj instanceof Enum) {
            map.put(obj.getClass().getSuperclass(), obj);
        } else {
            map.put(obj.getClass(), obj);
        }
    }

    /**
     * 从上下文中,根据类名取出参数
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(Class<T> c) {
        Map<Class<?>, Object> map = context.get();
        if (map == null) {
            return null;
        }
        return (T) map.get(c);
    }

    /**
     * 清空ThreadLocal的数据
     */
    public static void clean() {
        context.remove();
    }
}

代码解析:

  • 都是 static 操作,类似 DateUtil 玩法
  • 记得每次请求线程后清理。可以 AOP 去清理,加个注解就行。因为同一个请求线程可能被业务方公用。

(完)

原文地址:https://www.cnblogs.com/Alandre/p/11145516.html

时间: 2024-12-18 14:58:00

真实项目中 ThreadLocal 的妙用的相关文章

Java算法之递归打破及在真实项目中的使用实例

开心一笑 刚才领导问开发:"你觉得这个项目的最大风险是什么",开发说:"加班猝死" , 气氛尴尬了一分钟!!! 提出问题 1.递归算法简单复习 2.如何实现递归算法与真实项目接口??? 3.如何打破递归算法??? 解决问题 1.首先练习下网上一些递归经典题 1 package com.hwy.test; 2 3 /** 4 * 递归函数测试 5 * Created by Ay on 2016/7/2. 6 */ 7 public class RecursionTes

Android省市区三级联动滚轮选择(真实项目中提取出来的组件)

最近项目要做一个,类似淘宝手机客户端的,选择收货地址的三级联动滚动选择组件,下面是它的大致界面截图: 在IOS中有个叫UIPickerView的选择器,并且在dataSource中定义了UIPickerView的数据源和定制内容,所以用只要熟悉它的基本用法,要实现这么个三级联动滑动选择是挺简单的. 言归正传,今天讨论的是在Android里面如何来实现这么个效果,那么如何实现呢??? 相信部分童鞋首先想到的是android.widget.DatePicker和android.widget.Time

轨迹系列——记某真实项目中轨迹存储改造方案

文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.    方案目标 该方案需要满足以下几点: 支持人员当天轨迹快速获取(查询). 支持轨迹高并发读.写(实际项目中轨迹高并发读情况很少). 保证所有(历史)轨迹数据的完整性.不丢失. 2.方案探讨详细描述 2.1支持轨迹快速查询--轨迹日志文件方案 海量数据高效存储.查询,这个场景本身是比较适合NoSQL数据库运用的,但是考虑到该方案实施的难度(对工程实施.维护.研发

从真实项目中抠出来的设计模式——第三篇:责任链模式

一:现实场景 有时候在开发的过程中,我们经常会根据某个状态的值,写出很多的ifelse逻辑,比如拿项目里面的案例来说,如果当前发送的是彩信,此种状态需要如何给 实体赋值,如果是短信,邮件又是其他方式的赋值,等等此类,这种情况下一般会写出如下if判断,对吧,真实代码如下: 1 if (leaflet.CommunicationtypeEnum.HasFlag(CommunicationTypeEnum.邮件)) 2 { 3 //第三步:动态生成邮件模板 4 var styleInfo = Cach

真实项目中VS2015中自建T4模板生成文件的使用

有可能许多小伙伴们发现,vs2015和2012的自带T4模板中的.tt文件改变非常之多,如果仅仅copyEF系统自己生成的模板文件,那可累了.以下是我自己整理的在2012和2015中都可以试用的代码. <#@ template language="C#" debug="false" hostspecific="true"#> <#@ include file="EF.Utility.CS.ttinclude"

从真实项目中抠出来的设计模式——第二篇:过滤器模式

一:实际场景介绍 我们在给用户做订单催付通知的时候,会有这样的一种场景,用户在系统后台设置一组可以催付的规则,比如说订单金额大于xx元,非黑名单用户,来自 哪个地区,已购买过某个商品,指定某个营销活动的人等等这样的条件,如果这时用户在淘宝上下了一个订单,那程序要判断的就是看一下此订单是否满足这 些规则中的某一个,如果满足,我们给他发送催付通知,这种场景是很多做CRM的同学都会遇到的问题,那针对这种场景,如何更好的规划业务逻辑呢? 二:普通的编程代码 在这里我们就不考虑多筛选条件下的性能,而只从代

真实项目中,模块化javascript

为了和10年前的老项目在技术上和逻辑上完全抽离,可以通过模块化js的方式和老项目进行衔接: //竞品车系相关js //JS 模块 var jingpinMain = { //下拉框数量 selectCount : 1, //相关使用api URL: { //获取全部品牌 getCarBrandUrl: function () { return "/NewPost/GetCarBrand/"; }, //品牌获取车系url getCarSericesUrl: function () {

VUE真实项目中常用的生命周期和参数

<template> <div class="first-app"> {{msg}} <br/> {{msg1}} <Confin text='注册' @message="getMeaasge"></Confin> </div> </template> <script> import Confin from '@/components/sub/Confin'; expor

大型验证项目中的数据管理

? Eduardo Lluna拥有巴伦西亚大学的物理学位和巴伦西亚大学理工学院电子工程系的博士学位.他一向对于软件测试,电子测量设备和感测网路方面的研究都很积极.Lluna有20多年的关于交通控制,公众视野,支付,电信系统领域的即时嵌入系统的软件的专业经验.他曾在SICE, Telvent还有 Design of Systems on Silicon (DS2)公司任过职.他是巴伦西亚大学计算机科学部的副教授,且自2011年1月起,就已在ITI公司的软件质量部担任协助不同研发项目的项目经理.更多