ThreadLocal是Java语言提供的一个线程局部(Thread-Local)变量。当你在多线程环境下工作时,每个线程可以通过ThreadLocal存储属于自己的对象副本,各个线程间的这些副本是相互独立的。这意味着你无需同步就可以保证字段不会出现并发访问问题。
理解ThreadLocal
ThreadLocal的常见用途包括:
- 维护数据库连接、会话信息、简单的性能监视统计等线程特定的数据。
- 在没有依赖注入容器的情况下管理线程作用域的数据。
每个线程调用ThreadLocal的get()方法时,都会返回与当前线程关联的对象副本。如果是第一次调用ThreadLocal的get()方法,且之前没有调用过set()方法,则ThreadLocal通常会调用它的initialValue()方法,以便为该线程提供一个初始值。
源码分析
ThreadLocal内部是通过一个ThreadLocalMap来维持线程局部变量的。每个Thread都有一个ThreadLocalMap引用,该Map的键是ThreadLocal实例,值是对应的线程局部实例。
下面是ThreadLocal的一个简单使用示例和一部分实现源码分析:
示例代码
public class ThreadLocalExample {
// 创建一个Integer类型的线程局部变量
private static final ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
public static class MyRunnable implements Runnable {
@Override
public void run() {
threadLocalValue.set((int) (Math.random() * 100D));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocalValue.get());
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); // 等待线程1终止
thread2.join(); // 等待线程2终止
}
}
在上面的代码中,我们创建了一个ThreadLocal实例,并给它一个初始值0。然后,我们创建了一个实现Runnable接口的类,在该类的run方法中改变了threadLocalValue的值,并打印出来。
源码分析(以 OpenJDK 8 为例)
public class ThreadLocal<T> {
// 线程局部变量的值存储在每个线程自己的 threadLocals 中,而不是ThreadLocal对象中。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 我们的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
// get 方法获取与当前线程关联的值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
...
}
ThreadLocal实际上并不存储值,它只是作为一个键,通过get()、set()方法操作Thread内部的ThreadLocalMap。这个Map由多个Entry组成,Entry是ThreadLocal的内部类,它扩展了WeakReference,用于作为键的引用。这是因为ThreadLocal可能会被GC回收,而不影响线程生命周期内的使用。
细节说明
ThreadLocal在使用完毕后应该调用remove()方法,以防止内存泄漏。尤其是在使用线程池时,线程是会被重用的,如果不清理,可能会导致之后执行的任务使用到旧数据。ThreadLocal的键(即ThreadLocal对象)是弱引用,但值不是。如果ThreadLocal没有外部强引用且被GC,其Entry的键会变为null,这时ThreadLocalMap会将这些键为null的Entry称为"stale entries",它们的值不会被GC,直到下一次ThreadLocalMap调整或显式调用remove()。ThreadLocal不应该用于实例变量,它通常作为静态字段使用。如果你把它放在实例变量中,并在一个线程中操作,那么它会与那个线程绑定,并不会因为实例的改变而改变。
ThreadLocal是实现线程安全的一个重要工具,但使用时需要注意内存泄漏的问题,并确保不滥用,因为每个ThreadLocal在每个线程中都会保存一个副本,如果数据量大的话,会占用相应更多的内存。