了解ThreadLocal 的原理及示例学习

一、认识ThreadLocal

ThreadLocal 是一种线程本地变量的实现方式。它的核心思想是:为每个线程都创建一个独立的变量副本,这样每个线程只能访问到自己线程中的副本,而无法访问到其他线程的副本,从而实现了线程之间的隔离。这种方式有点像“拿空间换时间”,通过为每个线程分配独立的存储空间,避免了线程之间的竞争和同步开销,相比使用 synchronized 的方式,性能更高。

二、ThreadLocal 的底层

ThreadLocal 内部有一个静态内部类 ThreadLocalMap,它是一个基于哈希表的存储结构,用来保存线程本地变量的副本。ThreadLocalMap 中有一个 Entry 数组,每个 Entry 保存了一个键值对:

  • Key:指向 ThreadLocal 对象的弱引用(Weak Reference)。这意味着当 ThreadLocal 对象没有其他强引用时,它可能会在垃圾回收时被回收。

  • Value:线程本地变量的实际值。

三、Thread Local存在的缺点 

虽然 Entry 的键(Key)是弱引用,可以在垃圾回收时被回收,但仍然可能会出现内存泄漏的问题。具体来说:

  • ThreadLocal 对象被垃圾回收后,Entry 的键(Key)会变成 null,但此时 Entry 的值(Value)可能仍然存在。

  • 如果线程继续运行,这些键为 null 但值仍然存在的 Entry 无法被访问,也无法被垃圾回收,从而导致内存泄漏。

四、解决办法及示例 

建议在使用完 ThreadLocal 后,手动调用 remove() 方法来删除对应的 Entry,释放内存。

public class UserSession {
    // 定义一个 ThreadLocal 变量
    private static final ThreadLocal<String> userId = new ThreadLocal<>();

    // 设置用户 ID
    public static void setUserId(String id) {
        userId.set(id);
    }

    // 获取用户 ID
    public static String getUserId() {
        return userId.get();
    }

    // 清除用户 ID
    public static void clearUserId() {
        userId.remove();
    }
}

// 线程任务
class UserTask implements Runnable {
    private String userId;

    public UserTask(String userId) {
        this.userId = userId;
    }

    @Override
    public void run() {
        // 设置用户 ID
        UserSession.setUserId(userId);
        System.out.println("Thread " + Thread.currentThread().getName() + " is processing user: " + UserSession.getUserId());

        // 模拟业务逻辑
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 清理用户 ID
        UserSession.clearUserId();
        System.out.println("Thread " + Thread.currentThread().getName() + " has cleared user ID.");
    }
}

public class ThreadLocalExample {
    public static void main(String[] args) {
        // 创建线程
        Thread t1 = new Thread(new UserTask("user1"), "Thread-1");
        Thread t2 = new Thread(new UserTask("user2"), "Thread-2");

        // 启动线程
        t1.start();
        t2.start();
    }
}

示例解释:

  1. ThreadLocal 的使用
    每个线程通过 UserSession.setUserId() 设置自己的用户 ID。每个线程通过 UserSession.getUserId() 获取自己的用户 ID。每个线程在任务完成后通过 UserSession.clearUserId() 清理自己的用户 ID。
  2. 线程隔离
    线程 1 和线程 2 各自操作自己的用户 ID,互不干扰。ThreadLocal 为每个线程创建了一个独立的用户 ID 副本,存储在 ThreadLocalMap 中。
  3. 内存泄漏的预防
    在线程任务结束时,调用了 UserSession.clearUserId(),这会删除 ThreadLocalMap 中对应的 Entry,避免了内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值