ThreadLocal
ThreadLocal 是一种线程局部变量,它允许每个线程在自己的作用域内存储和访问变量。ThreadLocal 变量的值只能被当前线程所访问和修改,其他线程无法访问该变量。
ThreadLocal 提供了一种方便的方式来管理多线程环境下的共享状态,例如在 Web 应用程序中,可以使用 ThreadLocal 来存储用户会话 ID,以便在多个请求之间共享。ThreadLocal 还可以用于实现线程安全的数据结构,例如计数器或队列。
使用 ThreadLocal 时需要注意以下几点:
- 每个 ThreadLocal 实例都是独立的,因此需要为每个线程创建一个 ThreadLocal 对象。
- ThreadLocal 变量的默认初始值为 null,因此需要显式地设置初始值。
- 在不再需要 ThreadLocal 变量时,应该及时清除其引用,以避免内存泄漏
public class ThreadLocal<T> {
...
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
...
}
ThreadLocal 将自己作为key,将数据存入ThreadLocalMap 中
ThreadLocalMap
什么是ThreadLocalMap
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//------------------------------------------------------//
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap 是 Thread 的成员变量
所以 Thread ThreadLocalMap 与 ThreadLocal 关系如下:
- Thread 与 ThreadLocalMap是一对一,一个线程维护一个ThreadLocalMap
- Thread 与 ThreadLocal是一对多,一个线程可以拥有多个ThreadLocal对象,分别hash映射
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
通过构建 entity 保存数据,其中作为key的ThreadLocal,是WeakReference弱引用。
为什么使用弱引用
由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
而且ThreadLocalMap在内部的set,get和扩容时都会清理掉泄漏的Entry(Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。)内存泄漏完全没必要过于担心用
但是在特殊情况下,例如配合线程池使用时,由于线程一直存在,若未及时进行remove,会导致内存溢出。
1.线程池是长生命周期,使用完后不会主动释放资源
2.Thread–>ThreadLocalMap–>Entry[ ] key,value() key是弱引用,value是强引用,垃圾回收器不会回收value资源,内存无法释放
使用场景
web系统中,保存用户的user信息,在多个service之间传递
class service1 {
public void method1() {
System.out.println(" service1 get session "
+ SessionContextHolder.holder.get());
service2 service2 = new service2();
service2.method1();
}
}
class service2 {
public void method1() {
System.out.println(" service2 get session "
+ SessionContextHolder.holder.get());
service3 service3 = new service3();
service3.method1();
}
}
class service3 {
public void method1() {
System.out.println(" service2 get session "
+ SessionContextHolder.holder.get());
}
}
class SessionContextHolder {
public static ThreadLocal<String> holder = new ThreadLocal<>();
}
参考文章
ThreadLocal内存泄漏案例分析实战: https://juejin.cn/post/6982121384533032991
ThreadLocal https://blog.csdn.net/m0_46628950/article/details/126448853