真实项目中 ThreadLocal 的妙用

1、什么是 ThreadLocal

ThreadLocal 提供了线程的局部变量,每一个线程均可以经过 set() 和 get() 来对这个局部变量进行操做,但不会和其余线程的局部变量冲突,实现了线程间的据隔离。数据库

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

最简单的使用方式:

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

private ThreadLocal httpThreadLocal = new ThreadLocal();

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

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

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

56330542aaf5c815e1a027671c21435d.png

Thread 与 ThreadLocal 对象引用关系图异步

2、你熟悉的场景

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 是线程不安全的。这样会致使多个请求公用一个链接。请求量很大的状况下,延迟各类。你懂。code

所以,使用 ThreadLocal 保证每一个请求线程的 Connection 是惟一的。即每一个线程有本身的链接。对象

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

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

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

3、实战场景

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

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

简单的例子:

适用知足这两个条件的场景: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 去清理,加个注解就行。由于同一个请求线程可能被业务方公用。

(完)

file

相关文章
相关标签/搜索