ThreadLocal

ThreadLocal是Java中用于线程局部变量的类,确保每个线程拥有独立的副本,避免线程安全问题。与synchronized不同,ThreadLocal不用于同步,而是用于数据隔离。在多线程环境中,ThreadLocal通过为每个线程创建变量副本,实现线程间的独立存储。然而,如果不正确使用,特别是在线程池中,可能会导致内存泄露。为防止内存泄露,应在使用完毕后调用ThreadLocal的remove方法。了解ThreadLocal的工作原理和管理策略对于优化并发程序至关重要。

 

ThreadLocal使用场景

ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全

ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念。

ThreadLocal与Synchonized的区别

ThreadLocal<T>其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

ThreadLocal 的 set 方法

为当前线程的 ThreadLocalMap 对象添加键值对,键为 ThreadLocal 对象,值为设置的 value 对象。

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 已经调用过set或get方法时,往线程的map中添加新的值,键为当前ThreadLocal对象
    if (map != null)
        map.set(this, value);
    // 没有调用过set或get方法时,为当前线程创建一个ThreadLocalMap
    // 并添加第一个值,键为当前ThreadLocal对象
    else
        createMap(t, value);
}

// 返回Thread对象t的ThreadLocalMap属性,初始为null
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal 的 get 方法

线程中调用 get 方法时,如果之前用 set 方法为这个 ThreadLocal 键添加过值,那么就返回设置过的值,否则就返回默认初始值 null。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 已经调用过set或get方法时,对象值已经设置过,就返回上一次的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 没有调用过set或get方法时,对象值没有设置过,就自动设置初始值,并返回初始值
    return setInitialValue();
}

private T setInitialValue() {
    // 获得初始值
    T value = initialValue();
    // set方法
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocal 的 initialValue

通常可以在新建 ThreadLocal 对象时,采用匿名内部类的方式重写该方法,来设置共享的初始值。详细见参考资料1。

// 这是一个可重写的方法,用来设置ThreadLocal对象在所有线程中的初始值
protected T initialValue() {
    return null;
}

那为啥ThreadLocal依然内存泄露的危险?

value值会存在一条强引用链,当线程存活时,Thread->Thread.threadlocals->ThreadLocalMap->value。

所以只要线程结束了,那么对应的threadLocalMap一定会被回收,不存在内存泄露的风险。

但是要是使用的是线程池,线程存活时间长,那么线程的threadLocalMap也不会被回收,那么就一直会存在一条对value值的强引用,无法回收value,就会造成内存泄露。

在使用线程池和ThreadLocal时,如何避免内存泄露?

在每次使用完ThreadLocal时,便调用ThreadLocal的remove()方法,remove()方法会在map中删除整个entry。

在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

### ThreadLocal的使用场景 ThreadLocal适用于需要在多线程环境中为每个线程独立存储和操作数据的场景。常见的使用场景包括: - **线程上下文传递**:例如在Web应用中,每个请求由一个线程处理,可以通过ThreadLocal保存请求相关的上下文信息,如用户身份、事务对象等,避免频繁传递参数[^2]。 - **线程安全的变量管理**:当多个线程需要访问某个变量,但每个线程对该变量的操作互不影响时,可以使用ThreadLocal来避免同步开销[^3]。 - **资源持有**:某些资源(如数据库连接、Session等)需要在线程内部共享,但不希望多个线程之间共享,ThreadLocal可以很好地满足这种需求[^1]。 ### ThreadLocal的实现原理 ThreadLocal的实现机制主要依赖于线程内部的ThreadLocalMap结构: - 每个线程(Thread类)内部维护一个ThreadLocalMap对象,该Map的键为ThreadLocal实例,值为存储的变量副本[^4]。 - ThreadLocalMap使用**弱引用(WeakReference)**作为键,以避免内存泄漏。当ThreadLocal对象不再被外部引用时,垃圾回收器可以正常回收该对象,但需要注意Map中对应的值仍需手动清理[^5]。 - 每个ThreadLocal实例都有一个唯一的哈希码(threadLocalHashCode),用于在ThreadLocalMap中快速定位存储位置[^5]。 通过这种机制,每个线程访问同一个ThreadLocal变量时,实际上访问的是各自线程独立的变量副本,从而实现了线程安全。 ### 示例代码 以下是一个简单的ThreadLocal使用示例: ```java public class ThreadLocalExample { // 定义一个ThreadLocal变量 private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 创建多个线程进行测试 for (int i = 0; i < 3; i++) { new Thread(() -> { // 设置线程本地变量 threadLocal.set((int) (Math.random() * 100)); // 获取线程本地变量 System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); }).start(); } } } ``` 在这个例子中,每个线程都设置了不同的整数值,彼此之间互不影响。 ### 总结 ThreadLocal通过为每个线程提供独立的变量副本,解决了线程安全问题,并在某些场景下提升了性能。其核心在于每个线程内部维护的ThreadLocalMap结构,以及基于弱引用的键值对管理机制。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值