探索ThreadLocal的创新应用

218 阅读4分钟

引用

前面我有两篇文章介绍了ThreadLocal,明确了ThreadLocal是干什么的,并且弄清楚了他的内存泄漏风险和挑战,这里我将详细介绍一下ThreadLocal的一些应用和封装方法。

线程的专属储物柜:ThreadLocal

ThreadLocal的双刃剑:内存泄漏的挑战与对策

一、学会合理设置初始值

通过重写 initialValue() 方法,可以为 ThreadLocal 变量设置一个合理的初始值,这样可以避免在没有显式设置值的情况下 get() 方法返回 null

首先来说,正常我们要用到ThreadLocal的时候,实际一般情况我们都会封装一个工具类,那么在这个工具类里面,我们怎么设置初始值呢?跟我一起瞧瞧:

下面的案例用于切换数据源:

public class DynamicDataSourceHolder {

    private static final ThreadLocal<String> contextHolder = ThreadLocal.withInitial(() -> DataSourceConstant.DEFAULT_DS);

    /**
     * 设置数据源类型
     *
     * @param dataSourceType
     */
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    /**
     * 获取数据源类型
     */
    public static String getDataSourceType() {
        // 假如上面没有设置默认值,这里就需要增加空判断
        // if (contextHolder.get() == null) {
        //     return DataSourceConstant.DEFAULT_DS;
        // }
        return contextHolder.get();
    }

    /**
     * 清除数据源类型
     */
    public static void clearDataSourceType() {
        contextHolder.remove();
    }

}

注意:假如我们不设置默认值,那么在18,19,20,21行就需要增加空判断

二、使用 InheritableThreadLocal

当需要在创建子线程时传递父线程的 ThreadLocal 变量时,可以使用 InheritableThreadLocal。这样,子线程会自动继承父线程中的 ThreadLocal 变量值。

image.png

这玩意的用法和ThreadLocal大差不差,但是他最大的特点就是,儿子完美继承父亲的基因,但是又有自己的价值观,也可以理解为我们玩单机游戏,老是喜欢给自己创建副本,当然也要注意使用姿势,别炸内存了。

使用场景:在很多时候我们会启动一个异步线程,但是有希望前面设定的特定信息能够在后续线程中也可以使用,这个时候我们就可以考虑用一下InheritableThreadLocal

三、封装 ThreadLocal 变量

可以将 ThreadLocal 变量封装在一个实现了 AutoCloseable 接口的类中,然后在 try-with-resources 语句中使用它。这样可以确保在结束时自动清理 ThreadLocal 变量,避免内存泄漏。

3.1 实现步骤

  1. 定义一个类:创建一个类,这个类内部持有一个 ThreadLocal 变量。

  2. 实现 AutoCloseable 接口:在这个类中实现 AutoCloseable 接口,这样你就可以在 try-with-resources 语句中使用它。

  3. 在 close 方法中清理:在 AutoCloseable 的 close 方法中添加清理 ThreadLocal 的逻辑。

  4. 使用 try-with-resources 语句:在需要使用 ThreadLocal 变量的地方,使用 try-with-resources 语句,这样可以确保 close 方法在资源使用完毕后被调用。

3.2 使用案例

  • 实现封装代码
import java.io.IOException;

public class ThreadLocalResource<T> implements AutoCloseable {
    
    private final ThreadLocal<T> threadLocal;

    public ThreadLocalResource(T initialValue) {
        this.threadLocal = new ThreadLocal<>();
        this.threadLocal.set(initialValue);
    }

    public T get() {
        return threadLocal.get();
    }

    public void set(T value) {
        threadLocal.set(value);
    }

    @Override
    public void close() throws IOException {
        threadLocal.remove();
        System.out.println("###### 自动清理完毕");
    }
}
  • 使用方法
import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        /* 使用 try-with-resources 语句,确保 ThreadLocal 变量被自动清理 */
        try (ThreadLocalResource<String> resource = new ThreadLocalResource<>("我是新来的")) {
            // 在这里使用 resource
            System.out.println("初始值: " + resource.get());
            resource.set("我是后面来的");
            System.out.println("设置后的值: " + resource.get());
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 执行到这里已经自动清理了
        System.out.println("程序执行完毕");
    }
}
  • 执行结果

注意:从下面的执行结果可以看到当代码执行到16行的时候,已经完成了自动清理

初始值: 我是新来的
设置后的值: 我是后面来的
###### 自动清理完毕
程序执行完毕

四、总结

在上面介绍了三种使用ThreadLocal的方法,实际上,这三种使用方法是可以结合使用的,甚至可以三者一起结合,具体使用情况要看业务场景需要整理。

到这里关于ThreadLocal的介绍已经完结了,感兴趣的也可以去看看前面两篇。

希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。

同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。

感谢您的支持和理解!