ThreadLocal是什么?

ThreadLocal是Java中的一个类,位于java.lang包下。它提供了一种线程级别的变量存储机制,允许我们在每个线程中存储独立的数据副本。每个线程都可以访问和修改自己线程内部的副本,而不会干扰其他线程的数据。这对于需要在线程之间保留独立状态的场景非常有用。

通常情况下,多个线程共享同一个对象或变量,可能会导致线程安全问题。使用ThreadLocal可以避免这种共享状态导致的问题,因为每个线程都拥有自己的独立副本。

ThreadLocal的使用

使用ThreadLocal通常包含以下步骤:

  1. 创建ThreadLocal对象:首先,我们需要创建一个ThreadLocal对象,它将作为我们在每个线程中存储数据的容器。

ThreadLocal<T> threadLocal = new ThreadLocal<>();
  1. 设置线程的数据:我们可以通过set()方法在当前线程中设置数据。

threadLocal.set(value);
  1. 获取线程的数据:我们可以使用get()方法获取当前线程中存储的数据。

T value = threadLocal.get();
  1. 清除线程的数据(可选):在某些情况下,我们可能需要清除当前线程中的数据。可以使用remove()方法来清除。

threadLocal.remove();

ThreadLocal实现原理

看一下ThreadLocal源码的set(T)方法,发现先获取到当前线程,再获取ThreadLocalMap,然后把元素存到这个map中。

public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    //讲当前元素存入map
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal实现的秘密都在这个ThreadLocalMap了,可以Thread类中定义了一个类型为ThreadLocal.ThreadLocalMap的成员变量threadLocals。

public class Thread implements Runnable {
   //ThreadLocal.ThreadLocalMap是Thread的属性
   ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap既然被称为Map,那么毫无疑问它是<key,value>型的数据结构。我们都知道map的本质是一个个<key,value>形式的节点组成的数组,那ThreadLocalMap的节点是什么样的呢?

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
​
    //节点类
    Entry(ThreadLocal<?> k, Object v) {
        //key赋值
        super(k);
        //value赋值
        value = v;
    }
}

这里的节点,key可以简单低视作ThreadLocal,value为代码中放入的值,当然实际上key并不是ThreadLocal本身,而是它的一个弱引用,可以看到Entry的key继承了 WeakReference(弱引用),再来看一下key怎么赋值的:

public WeakReference(T referent) {
    super(referent);
}

key的赋值,使用的是WeakReference的赋值。

简单总结如下:

  • Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,每个线程都有一个属于自己的ThreadLocalMap。

  • ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal的弱引用,value是ThreadLocal的泛型值。

  • 每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

  • ThreadLocal本身不存储值,它只是作为一个key来让线程往ThreadLocalMap里存取值。

ThreadLocal的应用场景

ThreadLocal常用于以下场景:

  1. 保存线程特有的上下文信息:比如用户认证信息,请求信息等。这样在整个请求处理过程中,不需要在方法间传递这些信息,每个方法都可以直接访问到所需数据。

  2. 线程安全的数据共享:有时候需要在多个方法中共享一些数据,但这些数据不是线程安全的。通过使用ThreadLocal,我们可以实现线程安全的数据共享,每个线程拥有自己独立的数据副本,不会相互干扰。

  3. 代替参数传递:在复杂的业务逻辑中,有时候会有很多参数需要传递,使用ThreadLocal可以避免方法签名中过多的参数,使代码更加简洁。

需要注意的是,ThreadLocal虽然能够在一定程度上解决共享状态的问题,但过度使用也可能导致资源泄漏和难以调试的问题。因此,在使用ThreadLocal时,要谨慎设计和确保在合适的时候清理线程的数据,以避免潜在的问题。