如何避免忘记清理 ThreadLocal ?

简介: hreadLocal 可以解决“线程安全问题”。也可以作为上下文暂存数据以备后续步骤获取。但是 ThreadLocal 用不好的确容易产生故障,因而有些团队不允许使用 ThreadLocal。最核心的一个原因是很容易忘记清理,在线程池环境下复用导致串环境。那么,有什么优雅的解法没?本文给出自己的一个解法。

一、背景

ThreadLocal 可以解决“线程安全问题”。
在这里插入图片描述

也可以作为上下文暂存数据以备后续步骤获取。

但是 ThreadLocal 用不好的确容易产生故障,因而有些团队不允许使用 ThreadLocal。

最核心的一个原因是很容易忘记清理,在线程池环境下复用导致串环境。

在这里插入图片描述

那么,有什么优雅的解法没?本文给出自己的一个解法。

二、解法

package basic.thread;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;

public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /**
     * 初始化上下文
     */
    public static void initContext() {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else {
            CONTEXT.get().clear();
        }
    }

    /**
     * 清除上下文
     */
    public static void clearContext() {
        CONTEXT.remove();
    }

    /**
     * 获取上下文内容
     */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /**
     * 设置上下文参数
     */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8));
            con = CONTEXT.get();
        }
        con.put(key, value);
    }
}

2.1 Java 开发手册中的建议

在这里插入图片描述

写入如下:

    public Result<R> executeAbility(T ability) {
           //初始化上下文
            ThreadContext.initContext();
        try {
            //省略核心业务代码

        } finally {
            ThreadContext.clearContext();
        }
    }

2.2 进一步改进

相信绝大多数人会止步于此,但我认为这还是不够的。
如何才能避免忘掉清理 threadlocal 呢?

JDK 源码中有没有类似的案例呢?
想想IO 读写文件后,也是需要采用类似的做法去释放资源,JDK 提供了 try-with-resource 让释放资源更简单,使用者不需要手动写 finnaly 去释放资源。

普通案例:
在这里插入图片描述
使用 try-with-resource
在这里插入图片描述

另外我们知道,可以通过实现 AutoCloseable 来自定义 try-with-resource 的资源。

但最后发现并不是很适配,因为在传递上下文这种场景下, ThreadLocal 工具类通常都是静态的,而且即使不适用静态,获取属性时还要将该对象传递下去,不是很方便。

当然,如果大家不想以静态的方式使用,也可以考虑实现 AutoClosebale 接口,使用 try-with-resource 的机制。

我们是否也可以采用类似的机制呢?

可以直接将初始化和清理方法私有化,提供无参和带返回值的封装,使用 Runnbale 和 Callable 将调用作为参数传入,在封装的方法中封装 try- finally 逻辑。

package basic.thread;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /**
     * 初始化上下文
     */
    private static void initContext() {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else {
            CONTEXT.get().clear();
        }
    }

    /**
     * 清除上下文
     */
    private static void clearContext() {
        CONTEXT.remove();
    }

    /**
     * 获取上下文内容
     */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /**
     * 设置上下文参数
     */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8));
            con = CONTEXT.get();
        }
        con.put(key, value);
    }

    /**
     * 自动回收的封装
     */
    public static void  runWithAutoClear(Runnable runnable){
        initContext();
        try{
            runnable.run();
        }finally{
            CONTEXT.remove();
        }
    }

    /**
     * 自动回收的封装
     */
    public static <T> T callWithAutoClear(Callable<T> callable){
        initContext();
        try{
            try {
                return callable.call();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }finally{
            CONTEXT.remove();
        }
    }
}

使用参考:

   public Result<R> executeAbility(T ability) {
      return  AbilityContext.callWithAutoClear(()->{
                 // 业务核心代码
       });
    }

三、总结

只要思想不滑坡,办法总比困难多。

我们应该想办法去解决问题,而不是你回避问题。
当看到有些解决方案仍然容易出错时,应该想办法去做进一步的改进。

当然,如果不想使用 ThreadLocal 还想暂存对象给后续环节使用,可以定义上下文对象,在不同的执行步骤间传递。

类似的文章还有:《Map 有变动时触发特定行为实现》


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。 在这里插入图片描述
相关文章
|
NoSQL Java Redis
Spring boot整合Redis实现发布订阅(超详细)
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收信息。微信,微博,关注系统 Redis客户端可以订阅任意数量的频道
8153 0
Spring boot整合Redis实现发布订阅(超详细)
|
设计模式 安全 NoSQL
【设计模式】JAVA Design Patterns——Abstract-document(抽象文档模式)
【设计模式】JAVA Design Patterns——Abstract-document(抽象文档模式)
|
前端开发 网络协议 Dubbo
超详细Netty入门,看这篇就够了!
本文主要讲述Netty框架的一些特性以及重要组件,希望看完之后能对Netty框架有一个比较直观的感受,希望能帮助读者快速入门Netty,减少一些弯路。
91431 32
超详细Netty入门,看这篇就够了!
|
Java 数据库连接 程序员
从头到尾手把手教你搭建阅读Mybatis源码的环境(程序员必备技能)
从头到尾手把手教你搭建阅读Mybatis源码的环境(程序员必备技能)
341 0
|
消息中间件 NoSQL Kafka
订单超时取消的11种方式(非常详细清楚)
订单超时取消的11种方式(非常详细清楚)
7359 4
订单超时取消的11种方式(非常详细清楚)
|
JSON Java Apache
SpringCloud - Feign 调用服务及传递参数踩坑记录(上)
SpringCloud - Feign 调用服务及传递参数踩坑记录(上)
3161 0
|
11月前
|
存储 监控 安全
深入理解ThreadLocal:线程局部变量的机制与应用
在Java的多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将深入探讨`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
229 2
|
安全 Linux 网络安全
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
【工具使用】几款优秀的SSH连接客户端软件工具推荐FinalShell、Xshell、MobaXterm、OpenSSH、PUTTY、Terminus、mRemoteNG、Terminals等
118366 0
|
Java UED
基于SpringBoot自定义线程池实现多线程执行方法,以及多线程之间的协调和同步
这篇文章介绍了在SpringBoot项目中如何自定义线程池来实现多线程执行方法,并探讨了多线程之间的协调和同步问题,提供了相关的示例代码。
3513 0
|
安全 Java Spring
Spring Boot 关闭 Actuator ,满足安全工具扫描
Spring Boot 关闭 Actuator ,满足安全工具扫描
1651 0