为什么需要 ThreadLocal
线程是最小的执行单元,多线程可以在同一时间同时执行相同的代码。如果多个线程在同一个对象或者是实例上执行,那么这些线程将共享对象或实例中的属性。同时每个线程都有它们自己的局部变量,但是这些局部变量如果不通过参数的方式传递,很难进行共享。
通过例子来解释是最好。假设有一个 Servlet,这个 Servlet 首先会获取用户的信息,然后执行一些其他的动作。
doGet(HttpServletRequest req, HttpServletReresponse resp) {
User user = getLoggedInUser(req);
doSomething();
doSomethingElse();
renderResponse(resp);
}
如果现在 doSomething() 和 doSomethingElse() 也需要用户信息,这个时候你该怎么办呢?你不能够把 user 变成一个实例变量或者是静态变量,因为其他的线程也会执行相同的代码, 可能出现用户信息被替换的情况。这时你可能会把用户信息作为一个参数传递给 doSomething() 和 doSomethingElse():
doGet(HttpServletRequest req, HttpServletReresponse resp) {
User user = getLoggedInUser(req);
doSomething(user);
doSomethingElse(user);
renderResponse(resp);
}
这种方式虽然临时解决了问题,但是这会让每个需要用户信息的方法都新增用户参数,并且随着时间的推移,如果又需要一些其他的信息呢?又增加参数吗?肯定不能够这么做,这大大增加了代码的维护难度,非常的不优雅。
更为优雅的处理方式是将用户相关的信息放入到 ThreadLocal 中:
StaticClass.java
class StaticClass {
static private ThreadLocal<User> threadLocal = new ThreadLocal<>();
static ThreadLocal<User> getThreadLocal() {
return threadLocal;
}
}
doGet(HttpServletRequest req, HttpServletResponse resp) {
User user = getLoggedInUser(req);
StaticClass.getThreadLocal().set(user)
try {
doSomething()
doSomethingElse()
renderResponse(resp)
}
finally {
StaticClass.getThreadLocal().remove()
}
}
然后在需要用户信息的地方在将其取出来:
User user = StaticClass.getThreadLocal().get()
什么是 ThreadLocal?
ThreadLocal 是 Java 提供的一个类,用于创建和管理线程的局部变量。每个线程都可以独立的访问和修改线程中的变量,而不影响其他的线程。ThreadLocal 的主要作用就是在多线程的环境下保存每个线程独有的数据,线程间互不影响,从而避免锁机制所带来的复杂性和额外的性能开销。
ThreadLocal 类似于一个中间件,它把对线程局部变量的操作进行了封装,通过 ThreadLocal 我们可以非常方便和优雅的访问和管理同一个线程中的局部变量。
ThreadLocal 的使用
有两种方式初始化 ThreadLocal:
// 1. 如果你希望局部变量有一个默认的初始值,可以使用这种方式
ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> 2);
// 2. 没有默认值,直接 new 一个对象
ThreadLocal<integer> threadId = new ThreadLocal<>();
使用示例:
public class UserContext {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static User getCurrentUser() {
return userThreadLocal.get();
}
public static void setCurrentUser(User user) {
userThreadLocal.set(user);
}
public static void clear() {
userThreadLocal.remove();
}
}
ThreadLocal 实现原理
前置代码
Thread.java
public class Thread implements Runnable {
// ......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
// ......
}
ThreadLocal.java
public class ThreadLocal<T> {
// ......
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
protected T initialValue() {
return null;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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;
}
// ......
}
设置元素的流程
public class ThreadLocal<T> {
// .....
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// key 是 ThreadLocal
map.set(this, value);
} else {
createMap(t, value);
}
}
// .....
}
获取元素的流程
public class ThreadLocal<T> {
// ......
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// key 是 ThreadLocal
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// ......
}
删除元素
public class ThreadLocal<T> {
// ......
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
// ......
}
通过上述源码阅读可以知道,ThreadLocal 的关键就是 Thread 类里面的 threadLocals 属性。通过 ThreadLocal 声明的线程局部属性都会保存在 Thread 类的threadLocals 属性中,key 为 ThreadLocal,value 为用户设定的值。
InheritableThreadLocal
InheritableThreadLocal 对 ThreadLocal 进行了扩展,允许在创建子线程的时候将父线程中已存在的线程局部变量继承到子线程中。继承后,父线程和子线程对局部变量的修改是独立的,互不影响。需要注意的是,子线程创建后,父线程中对局部变量的修改,子线程是无法感知的。这点需要注意,接下来是使用示例:
public class InheritableThreadLocalDemo {
private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("mainThread");
System.out.println("value:"+threadLocal.get());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String value = threadLocal.get();
System.out.println("value:"+value);
}
});
thread.start();
}
}
执行结果:
value:mainThread
value:mainThread
如果将上述代码中 InheritableThreadLocal 改成 ThreadLocal, 则子线程中输出的值为 null
public class InheritableThreadLocalDemo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("mainThread");
System.out.println("value:"+threadLocal.get());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String value = threadLocal.get();
System.out.println("value:"+value);
}
});
thread.start();
}
}
执行结果:
value:mainThread
value:null
潜在的问题
ThreadLocal 虽然带来了很多的便利,但是使用不当的话,会导致内存泄漏,尤其是在 Thread 生命周期特别长情况下,比如线程池中的线程。如果当前线程不需要 ThreadLocal 后,应该使用 ThreadLocal 的 remove() 方法手动删除 ThreadLocal 存储的数据。
在哪些场景下使用
- 非线程安全的对象:Java 中的很多类都是非线程安全的,例如 SimpleDateFormat,NumberFormat. 通过 ThreadLocal 来使用这些类,可以让每个线程都拥有它们自己的实例,从而避免并发相关的问题。
- 线程特定上下文数据:需要在应用程序的不同层(方法)中访问的数据可以使用 ThreadLocal,比如用户信息,国际化设置或者是其他的上下文信息。这样可以避免上下文数据通过方法参数的方式到处传递,提升代码的可维护性。