ThreadLocal原理
- 基本概念:ThreadLocal 是Java中的一个类,它为每个使用该变量的线程都提供一个独立的变量副本,使得每个线程在访问该变量时,实际操作的是自己的副本,而不会影响其他线程的副本,从而在一定程度上避免线程安全问题。
- 实现机制:
- 内部结构:ThreadLocal内部有一个静态内部类ThreadLocalMap。每个Thread对象中都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals 。当通过ThreadLocal的set方法设置值时,实际上是往当前线程的threadLocals这个ThreadLocalMap中存储键值对,键为当前ThreadLocal实例,值为设置的值。例如:
public class ThreadLocalExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
threadLocal.set("Thread1 value");
System.out.println("Thread1: " + threadLocal.get());
}).start();
new Thread(() -> {
threadLocal.set("Thread2 value");
System.out.println("Thread2: " + threadLocal.get());
}).start();
}
}
- **get方法原理**:当调用ThreadLocal的get方法时,首先获取当前线程的threadLocals,然后以当前ThreadLocal实例为键去ThreadLocalMap中获取对应的值。如果ThreadLocalMap中没有找到对应的值,会调用initialValue方法返回一个初始值(默认返回null)。
- **set方法原理**:set方法首先获取当前线程的threadLocals,然后以当前ThreadLocal实例为键,将要设置的值为值,存储到ThreadLocalMap中。如果键已经存在,则更新对应的值。
高并发场景下的内存泄漏风险
- 原因:ThreadLocalMap使用弱引用(WeakReference)来保存ThreadLocal实例作为键。当ThreadLocal对象在外部没有强引用指向它时,在垃圾回收时,这个ThreadLocal实例可能会被回收,但是ThreadLocalMap中对应的Entry的键会变成null,而值还是强引用,这就导致值无法被回收,造成内存泄漏。
- 示例代码说明内存泄漏风险:
public class MemoryLeakExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
threadLocal.set("Value");
// 模拟外部不再持有ThreadLocal实例的强引用
threadLocal = null;
// 此时如果没有正确清理,threadLocalMap中的值可能无法回收
});
thread.start();
}
}
避免内存泄漏风险的方法
- 手动调用remove方法:在使用完ThreadLocal后,及时调用其remove方法,这样会从ThreadLocalMap中移除对应的Entry,从而避免内存泄漏。例如:
public class AvoidMemoryLeakExample {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
threadLocal.set("Value");
// 业务逻辑处理
} finally {
threadLocal.remove();
}
});
thread.start();
}
}
- 使用try - finally块:将ThreadLocal的使用放在try - finally块中,在finally块中调用remove方法,确保无论业务逻辑是否出现异常,都能正确清理ThreadLocalMap中的数据,避免内存泄漏。