【并发编程】ThreadLocal 实现线程的专属本地副本

目录

一、ThreadLocal

1.1 什么是 ThreadLocal?

1.2 ThreadLocal 的作用

1.3 API

常用方法介绍

1.4 开发规范

二、ThreadLocal 源码分析

2.1 ThreadLocal 内部实现

2.1 ThreadLocal.get

2.2 ThreadLocal.set

2.3 ThreadLocalMap

三、ThreadLocal 内存泄露问题

3.1 什么是内存泄漏?

3.2 引用的整体架构

3.3 内存泄露原因分析

四、父子线程共享传递

4.1 ThreadLocal

4.2 InheritableThreadLocal

4.3 TTL(TransmittableThreadLocal)


一、ThreadLocal

1.1 什么是 ThreadLocal?

ThreadLocal 提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问 ThreadLocal 实例的时候(通过 get 或 set 方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类的私有静态字段,使用它的目的是希望将状态(例如用户ID或事务ID)与线程关联起来。

1.2 ThreadLocal 的作用

ThreadLocal 线程本地变量,每个线程都拥有一份该变量的独立副本,即使是在多线程环境下,每个线程也只能修改和访问自己的那份副本,从而避免了线程安全问题,实现了线程间的隔离。

案例分析:不同的线程拥有自己的本地变量,卖不同的票数

public class House {

    // 使用 AtomicInteger 保证原子性(基于CAS):默认初始值为0
    private final AtomicInteger saleCount = new AtomicInteger(0);

    /**
     * 如果需要使用ThreadLocal,应该是static的
     * 通过Lambda表达式初始化,保证每个线程都有自己的副本
     */
    private static final ThreadLocal<Integer> saveVolume = ThreadLocal.withInitial(() -> 0);

    /**
     * 卖房
     */
    public void saleHouse() {
        saleCount.incrementAndGet(); // 原子操作
        saveVolume.set(saveVolume.get() + 1); // 每个线程有自己的副本
    }

    /**
     * 查看销售数
     * @return
     */
    public int getTotalSales() {
        return saleCount.get();
    }

    public static void main(String[] args) {
        // 创建一个共享的House实例
        House house = new House();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                int size = new Random().nextInt(5) + 1;
                System.out.println(Thread.currentThread().getName() + " 计划销售: " + size);

                for (int j = 0; j < size; j++) {
                    house.saleHouse();
                }
                // 每个线程都有自己保存的副本,不会相互干扰
                System.out.println(Thread.currentThread().getName() + "\t线程本地销售: " + house.saveVolume.get());
            }).start();
        }
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("总销售数量: " + house.getTotalSales());
    }
}

1.3 API

常用方法介绍

1. initialValue():返回线程局部变量的初始值

ThreadLocal<Integer> saveVolume = new ThreadLocal<>(){
    @Override
    protected Integer initialValue() {
        return 0;
    }
};

工作中不建议使用该方式,而是通过匿名内部类的方式。

2. withInitial():以匿名内部类的方式设置初始值

与上述的方法的作用相同,但是该匿名内部类的方式在使用上更加简洁。

ThreadLocal<Integer> saveVolume = ThreadLocal.withInitial(()->{
    return 0;
});

1.4 开发规范

规范手册规定:【强制】必须回收自定义的 ThreadLocal 变量,尤其是在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑,造成内存泄露的问题。建议合理通过 try-finally块进行回收。

  1. 因为每个 Thread线程 内有自己的实例副本且该副本只由当前线程自己使用,所以其他线程不能进行访问,那么就不存在多线程间共享的问题

  2. 我们可以通过统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的。


二、ThreadLocal 源码分析

2.1 ThreadLocal 内部实现

Thread、ThreadLocal、ThreadLocalMap 的关系:每个线程Thread内部都存在一个ThreadLocal.ThreadLocalMap。ThreadLocalMap是ThreadLocal的一个静态内部类。此处我们可以通过源码查看:

 1. 第一步,点击 Thread 源码:首先我们可以在 Thread 中发现 ThreadLocalMap 变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敖云岚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值