深入剖析 Java 并发工具 ThreadLocal

简介: 深入剖析 Java 并发工具 ThreadLocal

ThreadLocal是什么


ThreadLocal 是 Java 中的一个工具类,它提供了线程局部变量。意思是,它可以创建变量副本给每个线程使用,而每个线程访问变量副本时都是隔离的,互不干扰。


ThreadLocal 的主要作用有:


线程隔离:每个线程有自己的变量副本,互不影响。

避免传参:通过 ThreadLocal 传递数据,不需要在方法之间传递参数。

来看一个例子:

public class ThreadLocalExample {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void add() {
        threadLocal.set(threadLocal.get() + 1);
    }
    public static Integer get() {
        return threadLocal.get();
    }
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    add();
                    System.out.println(get());
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    add();
                    System.out.println(get());
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}


输出结果:

1

2

3

1

2

3

可以看到,每个线程都有 threadLocal 变量的独立副本,各自累加并打印自己的 threadLocal 值,互不影响。这就实现了线程隔离的效果。

如果不使用 ThreadLocal,采用共享变量的方式,像下面这样:

public class NoThreadLocalExample {
    private static int num;
    public static void add() {
        num++;
    }
    public static int get() {
        return num; 
    }
    public static void main(String[] args) {
        // ...
    }
}


输出结果会是乱序的,因为两个线程会竞争修改和读取共享变量 num:

1

3

2

5

4

这就是不使用 ThreadLocal 时多个线程之间会相互影响的问题。

ThreadLocal 的注意事项主要有三点:


内存泄漏风险:ThreadLocal 使用弱引用存放数据,如果没有外部强引用,在线程消亡后可能导致内存泄漏。所以使用完 ThreadLocal 后调用 remove() 方法清除数据。

线程安全:ThreadLocal 本身是线程安全的,但存放在 ThreadLocal 中的数据对象不一定是线程安全的。如果多个线程操作同一个数据对象,仍然需要考虑线程同步。

继承性:子线程无法获取父线程设置到 ThreadLocal 中的数据。每个线程都有自己的变量副本。

总结:

ThreadLocal 为每个线程提供了独立的变量副本,实现了线程隔离和避免传参的效果。但是也存在内存泄漏的风险,需要在使用结束后调用 remove() 方法清除数据。并且 ThreadLocal 只为单个线程提供变量副本,子线程无法获取父线程的数据。

ThreadLocal 适用于变量在线程间隔离且在方法调用链上下文传递的场景。它的出现让我们在高并发环境下可以更简单、更优雅地编写代码。


ThreadLocal 的内部实现原理是什么?


ThreadLocal 中使用 ThreadLocalMap 这个内部类来存放每个线程的副本变量。

ThreadLocalMap 使用线程的引用作为 key,变量副本作为 value 存储在一个散列表中。

当调用 ThreadLocal 的 get() 方法时,会先获取当前线程,然后再从这个线程关联的 ThreadLocalMap 中获取变量副本。set() 方法也是类似,会设置当前线程的 ThreadLocalMap 的值。

来看 ThreadLocal 的实现源码:

public T get() { 
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t); 
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); 
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; 
} 
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); 
}   
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


我们可以看到:


通过 Thread.currentThread() 获取当前线程

通过 getMap() 方法获取线程的 ThreadLocalMap 对象

如果 ThreadLocalMap 存在,则从中获取或设置线程局部变量的值

如果 ThreadLocalMap 不存在,则创建一个 ThreadLocalMap 对象,并与线程关联后再设置值。

所以,每个线程中都维护着一个 ThreadLocalMap 的引用,这个 ThreadLocalMap 中存储着以 ThreadLocal 为 key 的变量副本,实现了每个线程的变量隔离。

而 ThreadLocal 本身只是作为一个 key 来标识变量,并不持有具体的值,这也是它实现线程隔离的基础。

ThreadLocal 是 Java 中实现线程局部变量的重要机制。它为每个线程创建变量的副本,使每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

ThreadLocal 的实现原理是,它使用 ThreadLocalMap 这个内部类给每个线程维护一个变量副本的映射。ThreadLocalMap 使用线程的引用作为 key,变量副本作为 value 存储在一个散列表中。

当通过 ThreadLocal 访问或者设置变量副本时,会首先获取当前线程的引用,然后再通过这个线程去关联的 ThreadLocalMap 中获取或设置对应的值。这样就为每个线程创建了一个独立的变量副本,实现了线程隔离的效果。

ThreadLocal 的主要 API 只有 get()、set() 和 remove() 这三个方法。它简单而强大,理解透彻后会给我们的多线程编程带来许多便利。

ThreadLocal 的应用场景主要有:数据库连接、事务管理、用户上下文传递、避免传参等。它适用于变量在线程之间隔离且需要在方法调用链中传递的场景。

但是 ThreadLocal 的使用也需要注意三点:


内存泄漏:因为 ThreadLocal 使用弱引用,如果没有外部强引用变量副本的话,可能导致内存泄漏。所以使用结束后要调用 remove() 方法删除引用。

线程安全:存放在 ThreadLocal 中的对象必须是线程安全的,否则在多线程环境下会出现问题。

过度使用:滥用 ThreadLocal 会让代码难以理解和维护。


使用 ThreadLocal 实现事务管理:


定义 ThreadLocal 变量来存储 Connection:

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

在开始事务时从数据源获取 Connection,并存入 ThreadLocal:

public void beginTransaction() throws SQLException {
    Connection conn = DataSourceUtils.getConnection();
    connectionHolder.set(conn);
    conn.setAutoCommit(false);
}


在事务中使用 get() 方法从 ThreadLocal 获取 Connection:

public void doSomeOperation() {
    Connection conn = connectionHolder.get();
    // 使用 conn 执行 SQL 操作
}


在提交或回滚事务时,从 ThreadLocal 中获取 Connection 并提交或回滚:

public void commitTransaction() throws SQLException {
    Connection conn = connectionHolder.get();
    conn.commit();
    conn.close();
    connectionHolder.remove();
}
public void rollbackTransaction() throws SQLException {
    Connection conn = connectionHolder.get();
    conn.rollback();
    conn.close();
    connectionHolder.remove(); 
}


事务结束后调用 remove() 从 ThreadLocal 中移除 Connection。

这样通过 ThreadLocal 存取 Connection,可以实现事务与线程的绑定,并确保每个线程都有自己独立的 Connection 进行事务管理。

这是使用 ThreadLocal 实现简单的事务管理的思路,更 robust 的事务管理框架会涉及更多内容,但 ThreadLocal 仍然是实现线程隔离的关键。

相关文章
|
9天前
|
人工智能 缓存 监控
使用LangChain4j构建Java AI智能体:让大模型学会使用工具
AI智能体是大模型技术的重要演进方向,它使模型能够主动使用工具、与环境交互,以完成复杂任务。本文详细介绍如何在Java应用中,借助LangChain4j框架构建一个具备工具使用能力的AI智能体。我们将创建一个能够进行数学计算和实时信息查询的智能体,涵盖工具定义、智能体组装、记忆管理以及Spring Boot集成等关键步骤,并展示如何通过简单的对话界面与智能体交互。
191 1
|
3月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
173 0
|
3月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
309 83
|
3月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
263 83
|
4月前
|
Java 数据安全/隐私保护
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本项目提供了一套基于Java的图片处理教学方案,包含自定义图片生成、图像水印添加及合法电子凭证生成技术示例。
|
4月前
|
Java 数据安全/隐私保护 计算机视觉
银行转账虚拟生成器app,银行卡转账截图制作软件,java实现截图生成工具【仅供装逼娱乐用途】
本内容提供Java生成自定义图片的示例代码,涵盖基础图像创建、文本添加及保存功能,适合学习2D图形编程。包括教学示例图片生成、文本图层处理和数字水印技术实现方案。
|
4月前
|
安全 Java 编译器
JD-GUI,java反编译工具及原理: JavaDecompiler一个Java反编译器
Java Decompiler (JD-GUI) 是一款由 Pavel Kouznetsov 开发的图形化 Java 反编译工具,支持 Windows、Linux 和 Mac Os。它能将 `.class` 文件反编译为 Java 源代码,支持多文件标签浏览、高亮显示,并兼容 Java 5 及以上版本。JD-GUI 支持对整个 Jar 文件进行反编译,可跳转源码,适用于多种 JDK 和编译器。其原理基于将字节码转换为抽象语法树 (AST),再通过反编译生成代码。尽管程序可能带来安全风险,但可通过代码混淆降低可读性。最新版修复了多项识别错误并优化了内存管理。
1973 1
|
4月前
|
Java 物联网 数据处理
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
Java Solon v3.2.0 是一款性能卓越的后端开发框架,新版本并发性能提升700%,内存占用节省50%。本文将从核心特性(如事件驱动模型与内存优化)、技术方案示例(Web应用搭建与数据库集成)到实际应用案例(电商平台与物联网平台)全面解析其优势与使用方法。通过简单代码示例和真实场景展示,帮助开发者快速掌握并应用于项目中,大幅提升系统性能与资源利用率。
127 6
Java Solon v3.2.0 史上最强性能优化版本发布 并发能力提升 700% 内存占用节省 50%
|
5月前
|
缓存 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(3-1):并发共享问题的解决与分析
活锁:多个线程相互影响对方退出同步代码块的条件而导致线程一直运行的情况。例如,线程1的退出条件是count=5,而线程2和线程3在其代码块中不断地是count进行自增自减的操作,导致线程1永远运行。内存一致性问题:由于JIT即时编译器对缓存的优化和指令重排等造成的内存可见性和有序性问题,可以通过synchronized,volatile,并发集合类等机制来解决。这里的线程安全是指,多个线程调用它们同一个实例的方法时,是线程安全的,但仅仅能保证当前调用的方法是线程安全的,不同方法之间是线程不安全的。
93 0