Java并发编程之ThreadLocal详解

Java并发编程之ThreadLocal详解

什么是ThreadLocal?

ThreadLocal 是 Java 中用于实现线程本地变量的机制,它提供了一种让每个线程独立管理变量的方式。也就是说,ThreadLocal 可以为每个线程创建一个单独的变量副本,各个线程之间互不干扰。这种机制在多线程编程中很常见,它可以解决多线程条件下数据共享和线程安全的问题。

ThreadLocal 的使用方法

ThreadLocal 没有公开的构造方法,它通过静态方法 ThreadLocal.withInitial() 来创建一个变量副本。withInitial() 方法接受一个 Supplier 接口类型的参数,该接口的实现方法返回一个初始值。

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)

例如,创建一个 ThreadLocal 对象并初始化为字符串类型的空值。

ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "");

然后,就可以通过 get() 方法来取出当前线程保存的副本。如果当前线程没有保存任何副本,那么 get() 方法会通过调用 Supplier 的 get() 方法来获取一个初始值副本并保存。

String value = threadLocal.get();

反之,如果已有副本,则直接返回该副本。

String value = threadLocal.get();

还可以通过 set() 方法来设置当前线程保存的副本。

threadLocal.set("value");

当然,如果不再需要当前线程保存的副本,或者需要清理线程保存的全部副本,也可以调用remove方法。

threadLocal.remove();

ThreadLocal 的基本应用场景

完全解耦

ThreadLocal 可以让所有的线程访问不同的对象副本,从而实现了完全解耦。不同线程所访问的是不同的对象副本,彼此之间对该对象的修改不会互相影响,从而避免了共享对象的多线程并发访问冲突。

例如,在 Web 应用中,使用 ThreadLocal 可以在每个请求处理的时候,为该请求单独准备一个数据库连接(或者一个 DAO 对象),以避免不同请求之间发生冲突。

用于线程间的状态传递

ThreadLocal 还可以用于线程之间状态传递,每个线程保存自己的状态,然后将状态传递给其他线程。例如:

public class Demo {
    private static final ThreadLocal<Integer> THREAD_LOCAL = new ThreadLocal<>();

    public static void main(String[] args) {
        THREAD_LOCAL.set(1);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ": " + THREAD_LOCAL.get());
        }, "thread-1").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ": " + THREAD_LOCAL.get());
        }, "thread-2").start();
    }
}

在上面的示例中,我们先将 THREAD_LOCAL 的值设置为 1,然后分别启动了两个线程,这两个线程都通过 ThreadLocal 来访问 THREAD_LOCAL 的值。可以看出,它们都是访问到的各自的副本,而不是一个共享变量。因此,它们输出的结果也都是不相同的。

ThreadLocal 需要注意的问题

内存泄漏

由于 ThreadLocal 需要为每个线程单独创建一个副本,如果不及时清理这些副本,就很容易导致内存泄漏问题。

内存泄漏的产生是因为由于 ThreadLocalMap 中的 Entry 引用了ThreadLocal,而 ThreadLocal 又持有 Entry 的强引用,ThreadLocalMap 的生命周期与 Thread 一样长,而 ThreadLocal 却没有及时被回收,就会导致内存泄漏。

public class Demo {
    private static final ThreadLocal<List<Integer>> THREAD_LOCAL = new ThreadLocal<>();

    public static void main(String[] args) {
        THREAD_LOCAL.set(new ArrayList<>());
        // 模拟业务处理,假设需要往 THREAD_LOCAL 中添加多个值
        for (int i = 0; i < 1000000; i++) {
            THREAD_LOCAL.get().add(i);
        }
        THREAD_LOCAL.remove();  // 手动清理 ThreadLocal 对象
    }
}

在上面的示例中,我们在 main 方法中先将 THREAD_LOCAL 设置为一个新的 ArrayList 对象,并在随后的代码中不断向其中添加元素,模拟业务处理。由于这里没有手动清理 THREAD_LOCAL 对象,因此可能会导致内存泄漏。为了避免这种情况,我们可以在每个线程处理完业务后,手动清理 ThreadLocal 对象。

线程池中的 ThreadLocal 泄漏

在使用线程池的时候,如果某个 ThreadLocal 没有被及时清理,就有可能导致该 ThreadLocal 在线程池中的所有线程中复用,有可能导致线程不可重入、线程上下文状态混乱等问题。

总结

ThreadLocal 是解决并发问题的有效手段之一,它为多线程编程提供了一种应对策略,为我们避免了多线程数据共享和线程安全的问题。通过了解 ThreadLocal 的基本使用方法以及注意事项,我们可以在实际开发中更好地运用它。

示例

示例一 将 MySQL 连接对象存储在 ThreadLocal 中

public class ConnectionManager {
    private static final ThreadLocal<Connection> CONNECTION_HOLDER = ThreadLocal.withInitial(() -> {
        try {
            return DriverManager.getConnection(DBConfig.getUrl(), DBConfig.getUsername(), DBConfig.getPassword());
        } catch (SQLException e) {
            logger.error("获取数据库连接失败:{}", e.getMessage());
        }
        return null;
    });

    public static Connection getConnection() {
        return CONNECTION_HOLDER.get();
    }

    public static void closeConnection() {
        Connection connection = CONNECTION_HOLDER.get();
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                logger.error("关闭数据库连接失败:{}", e.getMessage());
            } finally {
                CONNECTION_HOLDER.remove();
            }
        }
    }
}

在上面的示例中,我们使用 ThreadLocal 来保证每个线程获取到的 Connection 对象都是独立的。在 getConnection() 方法中,我们通过 CONNECTION_HOLDER 获取当前线程保存的 Connection 对象,如果不存在则创建一个新的对象并返回;在 closeConnection() 方法中,我们先尝试关闭 Connection 对象,然后再及时清理 ThreadLocal 对象,以避免内存泄漏。

示例二 使用 ThreadLocal 保存请求信息

public class RequestContext {
    public static final ThreadLocal<String> USER_ID = ThreadLocal.withInitial(() -> null);
    public static final ThreadLocal<String> REQUEST_ID = ThreadLocal.withInitial(() -> null);
    public static final ThreadLocal<Date> REQUEST_TIME = ThreadLocal.withInitial(() -> null);
    // ...
}

在上面的示例中,我们通过 ThreadLocal 来保存请求信息,USER_ID 表示用户 ID,REQUEST_ID 表示请求 ID,REQUEST_TIME 表示请求时间。通过使用 ThreadLocal 可以保证每个线程获取到的请求信息都是独立的,这对于多线程环境中的请求处理非常重要。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发编程之ThreadLocal详解 - Python技术站

(0)
上一篇 2023年5月17日
下一篇 2023年5月17日

相关文章

  • Java多线程Thread类的使用及注意事项

    Java多线程Thread类的使用及注意事项 简介 Java是一种多线程语言,这意味着Java中的程序可以同时执行多个线程。Java程序中的每一个线程都有一个执行路径,并且可以同时执行多个任务。Java中的Thread类是用于创建和管理线程的类。 创建Thread对象 要创建一个Thread对象,可以使用以下构造函数: Thread() Thread(Run…

    多线程 2023年5月17日
    00
  • 对python多线程SSH登录并发脚本详解

    关于“对Python多线程SSH登录并发脚本”的完整攻略,我可以从以下几个方面进行讲解: 前置条件:在正式编写SSH登录并发脚本之前,我们需要掌握一定的Python编程语言基础、网络协议原理和SSH传输协议知识。此外,我们还需要准备安装并使用相关Python库,如paramiko、os、time、threading等。具体步骤如下: 掌握Python编程语言…

    多线程 2023年5月17日
    00
  • Python 多进程、多线程效率对比

    当需要提高 Python 程序执行效率时,很多程序员会考虑使用多线程或多进程技术来并行地执行任务。这两种技术都可以提高程序的并发能力,但是它们的实现方式和适用场景都有所不同。 在使用多线程和多进程之前,需要先了解它们的区别和联系。 多进程与多线程的区别 多进程:每个进程拥有独立的内存空间以及系统资源,进程之间的通信需要进行 IPC(进程间通信),因此开销比较…

    多线程 2023年5月16日
    00
  • 实例探究Python以并发方式编写高性能端口扫描器的方法

    实例探究Python以并发方式编写高性能端口扫描器的方法 什么是端口扫描? 端口扫描是一种黑客常用的技术,用于探测目标计算机的哪些端口是开放的,以此来得知该计算机的服务和应用程序。同时,端口扫描也是网络管理员和安全专家用来测试防火墙和检测网络安全漏洞的重要工具。 Python实现端口扫描 Python作为一门高级编程语言,具有简单易学、代码简洁、易于编写复杂…

    多线程 2023年5月16日
    00
  • java多线程读取多个文件的方法

    下面是详细讲解Java多线程读取多个文件的方法的完整攻略。 一、什么是多线程读取多个文件 在Java中,多线程读取多个文件指的是同时启动多个线程,每个线程读取不同的文件并进行处理,这样可以充分利用系统资源,提高读取文件的效率。 二、如何实现多线程读取多个文件 Java实现多线程读取多个文件的方法有很多,其中比较常见的方式有如下两种: 1. 使用Java Ex…

    多线程 2023年5月17日
    00
  • QT实现多线程两种方式案例详解

    这里我详细讲解一下“QT实现多线程两种方式案例详解”的攻略。 一、关于多线程 多线程指从计算机的角度上,单个程序可以同时执行多个线程,在每个线程里执行不同的任务。在实际应用中,多线程可以有效提高程序的性能,增强用户体验。 在QT中,多线程实现可以带来许多好处,比如应用程序更稳定、更快速,用户交互更流畅等等。 二、多线程实现方式 QT中实现多线程的方式主要有两…

    多线程 2023年5月17日
    00
  • 关于SpringBoot 使用 Redis 分布式锁解决并发问题

    关于SpringBoot使用Redis分布式锁解决并发问题的攻略可以分为以下几个步骤: 第一步:引入Redis相关依赖 在开发SpringBoot应用时,我们需要在pom.xml文件中添加对Redis的支持,可以参考下面的依赖: <dependency> <groupId>org.springframework.boot</gr…

    多线程 2023年5月16日
    00
  • nodejs中使用多线程编程的方法实例

    Node.js中使用多线程编程的方法实例 在 Node.js 中,我们可以通过使用多线程的方式,提高服务器的效率和性能。本文将介绍 Node.js 中使用多线程编程的方法,并提供两个示例说明。 Node.js中使用多线程的方法 在 Node.js 中,我们可以通过以下两种方式使用多线程: 1. Child Process Node.js 通过 child_p…

    多线程 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部