Java ThreadLocal的使用
ThreadLocal是什么?
ThreadLocal
是Java中的一个类,位于java.lang
包下。它提供了一种线程级别的变量存储机制,允许我们在每个线程中存储独立的数据副本。每个线程都可以访问和修改自己线程内部的副本,而不会干扰其他线程的数据。这对于需要在线程之间保留独立状态的场景非常有用。
通常情况下,多个线程共享同一个对象或变量,可能会导致线程安全问题。使用ThreadLocal
可以避免这种共享状态导致的问题,因为每个线程都拥有自己的独立副本。
ThreadLocal的使用
使用ThreadLocal
通常包含以下步骤:
创建
ThreadLocal
对象:首先,我们需要创建一个ThreadLocal
对象,它将作为我们在每个线程中存储数据的容器。
ThreadLocal<T> threadLocal = new ThreadLocal<>();
设置线程的数据:我们可以通过
set()
方法在当前线程中设置数据。
threadLocal.set(value);
获取线程的数据:我们可以使用
get()
方法获取当前线程中存储的数据。
T value = threadLocal.get();
清除线程的数据(可选):在某些情况下,我们可能需要清除当前线程中的数据。可以使用
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
常用于以下场景:
保存线程特有的上下文信息:比如用户认证信息,请求信息等。这样在整个请求处理过程中,不需要在方法间传递这些信息,每个方法都可以直接访问到所需数据。
线程安全的数据共享:有时候需要在多个方法中共享一些数据,但这些数据不是线程安全的。通过使用
ThreadLocal
,我们可以实现线程安全的数据共享,每个线程拥有自己独立的数据副本,不会相互干扰。代替参数传递:在复杂的业务逻辑中,有时候会有很多参数需要传递,使用
ThreadLocal
可以避免方法签名中过多的参数,使代码更加简洁。
需要注意的是,ThreadLocal
虽然能够在一定程度上解决共享状态的问题,但过度使用也可能导致资源泄漏和难以调试的问题。因此,在使用ThreadLocal
时,要谨慎设计和确保在合适的时候清理线程的数据,以避免潜在的问题。
- 感谢你赐予我前进的力量