ThreadLocal学习

ThreadLocal是线程局部变量,用于在多线程环境下安全地存储和访问变量。它通过ThreadLocalMap在每个线程中存储数据,键为ThreadLocal对象,值为存储的变量。使用弱引用来防止ThreadLocal对象本身导致内存泄漏,但若不及时移除ThreadLocalMap中的键,可能导致内存溢出,尤其是在线程池场景中。常见应用场景包括在Web服务中存储用户会话信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadLocal

ThreadLocal 是一种线程局部变量,它允许每个线程在自己的作用域内存储和访问变量。ThreadLocal 变量的值只能被当前线程所访问和修改,其他线程无法访问该变量。

ThreadLocal 提供了一种方便的方式来管理多线程环境下的共享状态,例如在 Web 应用程序中,可以使用 ThreadLocal 来存储用户会话 ID,以便在多个请求之间共享。ThreadLocal 还可以用于实现线程安全的数据结构,例如计数器或队列。

使用 ThreadLocal 时需要注意以下几点:

  1. 每个 ThreadLocal 实例都是独立的,因此需要为每个线程创建一个 ThreadLocal 对象。
  2. ThreadLocal 变量的默认初始值为 null,因此需要显式地设置初始值。
  3. 在不再需要 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 关系如下:

  1. Thread 与 ThreadLocalMap是一对一,一个线程维护一个ThreadLocalMap
  2. 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.youkuaiyun.com/m0_46628950/article/details/126448853

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值