基本概念和作用
在多线程编程中,线程隔离是一个常见的需求,ThreadLocal 就是为了解决这一问题而生。简而言之,ThreadLocal 是一个能够让变量在线程内部保持隔离的机制。每个线程都拥有自己的 ThreadLocal 对象副本,一个线程内的更新不会影响到其他线程。这种特性特别适合实现线程安全的“线程范围内”的数据存储。
ThreadLocal与线程同步机制的区别
与使用 synchronized
或 Lock
等线程同步机制确保变量在多线程之间安全访问的方式不同,ThreadLocal 提供了一种无锁的线程隔离机制。通过为每个线程提供独立的变量副本,从而消除了线程同步的需求,也就消除了性能上的考虑,因为不会存在线程竞争问题。
ThreadLocal的典型应用场景
- 用户身份信息存储,确保每个线程的用户信息不会相互干扰。
- 数据库连接管理,通过 ThreadLocal 确保每个线程拥有自己独立的数据库连接。
- 在框架开发中如 Spring 和 Hibernate 大量使用了 ThreadLocal 来实现线程安全。
实现原理
ThreadLocalMap和Entry
ThreadLocalMap 是 ThreadLocal 的一个内部类,ThreadLocalMap 使用了一种特殊的 Entry 类型来存储键值对。这个 Entry 类型使用了弱引用(WeakReference)来引用 key,即 ThreadLocal 对象。而 value 则存储了存放在 ThreadLocal 中的实际对象。
Thread
类
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;
...
}
从 Thread 源码中可以发现,Thread 类中存在 ThreadLocalMap
类型的成员变量,这样一个Map作为存储结构, Entry 的 key 是 ThreadLocal 对象的弱引用, value 是在该线程中ThreadLocal要存储的值。
当调用 ThreadLocal
类的 get()
方法时,会在当前线程的成员变量 ThreadLocalMap 中查询 key ( ThreadLocal 对象弱引用)对应的 value 值。
弱引用好处与内存泄漏问题
假设在业务代码中使用完 ThreadLocal,threadLocalRef 被回收了,由于 ThreadLocalMap 只持有 ThreadLocal 的弱引用,没有任何强引用指向 threadlocal 实例,所以 threadlocal 就可以顺利被 GC 回收,此时 Entry 中的 key = null。 但在没有手动删除这个 Entry 以及 CurrentThread 依然运行的前提下,仍存在有强引用链 threadRef -> currentThread -> threadLocalMap -> entry -> value,因此value 不会被回收,而这块 value 永远不会被访问到了,从而导致 value 内存泄漏的问题。 要避免这个问题,就必须保证当不再需要存放在 ThreadLocal 中的对象时,显式调用 ThreadLocal 的 remove() 方法。
使用方法
创建ThreadLocal实例
private static final ThreadLocal<MyObject> threadLocalInstance = new ThreadLocal<>();
ThreadLocal 实例通常使用 static final 修饰是为了确保每个线程都可以访问到相同的 ThreadLocal 实例。由于 ThreadLocal 是线程本地变量,每个线程都需要访问相同的实例,因此使用 static final 修饰确保所有线程都可以共享相同的实例。
存取ThreadLocal中的值
// 存入值
threadLocalInstance.set(new MyObject());
// 取出值
MyObject myObject = threadLocalInstance.get();
// 移除值
threadLocalInstance.remove();
代码示例
public class ThreadLocalExample {
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
threadLocal.set(1);
printThreadLocalValue();
threadLocal.remove();
}).start();
new Thread(() -> {
threadLocal.set(2);
printThreadLocalValue();
threadLocal.remove();
}).start();
}
private static void printThreadLocalValue() {
System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
}
}
总结
使用注意事项
- 始终记得在不需要使用 ThreadLocal 存储的对象时,调用 remove() 方法来移除。
- 考虑到垃圾回收和内存泄漏的问题,合理使用 ThreadLocal。
优缺点
优点:
- 提供线程隔离以确保线程安全。
- 提升性能,因为不需要额外的同步措施。
缺点:
- 不当使用可能会导致内存泄漏。
- 可能会使代码的可读性和可维护性降低。
ThreadLocal 是并发编程中的一个强大工具,如果能够合理使用,并注意避免内存泄漏的问题,它将成为你的多线程程序中的利器。
...