一文搞定ThreadLocal

ThreadLocal

我们需要知道关于ThreadLocal的几个问题先提出:

  1. ThreadLocal是什么?(介绍)

  2. 有什么用?(作用)

  3. 我该怎么使用它(使用)

  4. 它类的底层是怎样的?(底层)

  5. 它会有什么问题呢?(缺点)

介绍

直接在官网找到对该类的介绍:

该类提供线程局部变量。这些变量与其正常对应变量的不同之处在于,访问一个变量(通过其 getset 方法)的每个线程都有其自己的、独立初始化的变量副本。 ThreadLocal 实例通常是类中希望将状态与线程关联起来的私有静态字段(例如,用户 ID 或事务 ID)。

ThreadLocal (Java Platform SE 7 )

个人对上述文字的理解:ThreadLocal就是每个线程中的局部变量,每个线程中都要。可以通过set或get方法访问。

作用

我们知道它是单独存在每个线程中的,所以我们可以推测出一下的作用:

  1. 线程封闭性(Thread Confinement): ThreadLocal 可以用于将某个对象与当前线程关联起来,使得该对象只能被当前线程访问,而其他线程无法访问。这样的数据隔离能够提高多线程程序的安全性。

  2. 线程上下文共享: 在某些情况下,线程之间需要共享一些数据,但这些数据对于其他线程是不可见的。ThreadLocal 可以用于在线程内部传递数据,而不需要将数据暴露给其他线程。

  3. 避免传递参数: 使用 ThreadLocal 可以避免在方法调用链中频繁传递相同的参数。例如,在 Web 应用中,可以将当前用户的信息存储在 ThreadLocal 中,这样在整个请求处理过程中都可以方便地访问到,而不需要在每个方法中都传递用户对象。

  4. 简化线程安全问题: 使用 ThreadLocal 可以避免一些线程安全问题,因为每个线程都拥有自己的变量副本,不需要考虑线程间的竞争和同步。

使用

比如说,在一个系统,用来存储当前线程登录的用户信息。把它的方法封装成一个工具类来使用。

使用流程:

  1. 创建一个ThreadLocal对象

  2. 设置值

  3. 需要的时候,把值取出

  4. 退出系统的时候,移除变量值

//这里是将ThreadLocal封装成了一个工具类
public class UserHolder {
    //创建一个ThreadLocal对象
    //注意,这里的static修饰其实是非常重要的,下面说的它的问题中会提到
    //这里的static就相当是将ThreadLocal类进行了强引用,key和value不会被垃圾回收
    //所以我们要注意,在线程不使用的时候,调用remove方法,将value移除,不然就容易出现当前线程读取到之前使用线程中存储的数据了
    private static final ThreadLocal<User> tl = new ThreadLocal<>();
​
    //设置值
    public static void saveUser(User user){
        tl.set(user);
    }
​
    //取出值
    public static User getUser(){
        return tl.get();
    }
​
    //移除值
    public static void removeUser(){
        tl.remove();
    }
}

底层

直接先来看ThreadLocal类中的源码:

1、先看get方法:

public T get() {
    // 1. 获取当前线程
    Thread t = Thread.currentThread();
    // 2. 从当前线程获取线程局部变量
    // 可以看出,其实存储线程局部变量的是一个ThreadLocalMap类
    // 这个ThreadLocalMap类是在ThreadLocal类中的一个内部静态类
    // 这个ThreadLocalMap同时也是一个哈希表
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 3. 从ThreadLocalMap中获取与当前ThreadLocal对象相关联的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 4. 如果当前线程没有与ThreadLocal对象相关联的值,则调用setInitialValue()方法进行初始化
    return setInitialValue();
}

这里是getMap方法:

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

这里是setInitialValue方法:

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

createMap方法:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

这里是ThreadLocalMap构造函数:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //......
}

总结一下:

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

缺点/问题

对于ThreadLocal最经常的问题就是:ThreadLocal 内存泄露问题是怎么导致的?

我们先要了解一个知识就是:

  • ThreadLocal的key 是弱引用,value是强引用

  • 如果ThreadLocal没有被外部强引用的时候,在垃圾回收的时候,key会被清理,value不会。

  • 如果在ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 回收,这个时候就可能会产生内存泄露。

弱引用介绍:

如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

解决方案:通过ThreadLocal中的remove()方法,及时将value释放。

然后在这个最后,肯定有人提出,

为什么把ThreadLocal的key设置成弱引用呢?还会内存泄露吗?

  • 如果我们就当ThreadLocal 的key是强引用,当我们不再使用这个对象的时候,这个 ThreadLocalMap的引用仍然一直在,垃圾回收器不会回收这个对象,如果不去进行手动移除,同样会导致内存泄露。

  • 所以,我们知道,如果ThreadLocal的key 如果是强引用,会同样导致和value一样的内存泄露。

  • 所以将ThreadLocal的key设置为弱引用的目的是避免因为ThreadLocal使用不当而导致的内存泄漏问题。

小结

  1. ThreadLocal类提供线程局部变量

  2. 作用:实现线程上下文共享

  3. 使用:创建 -> 设置值 -> 取出值 ->移除值

  4. 每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

  5. 问题:ThreadLocal的key 是弱引用,value是强引用。需要注意当ThreadLocal不被引用的时候,使用remove将value移除

相关推荐

  1. ThreadLocal

    2024-03-30 22:18:02       21 阅读
  2. 彻底 Python 的 Exception 处理

    2024-03-30 22:18:02       20 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-30 22:18:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-30 22:18:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-30 22:18:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-30 22:18:02       20 阅读

热门阅读

  1. 蓝桥杯刷题--python-33-树状数组

    2024-03-30 22:18:02       19 阅读
  2. 2434. 使用机器人打印字典序最小的字符串

    2024-03-30 22:18:02       20 阅读
  3. 数据可视化之极坐标

    2024-03-30 22:18:02       17 阅读
  4. 安卓APP开发中的重要环节:数据和文件存储概述

    2024-03-30 22:18:02       19 阅读
  5. Golang- 邮件服务,发送邮件

    2024-03-30 22:18:02       21 阅读
  6. 【Docker】常用命令 docker compose

    2024-03-30 22:18:02       16 阅读
  7. C语言- %i 读取不同进制的数

    2024-03-30 22:18:02       14 阅读