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使用代码模拟高并发操作的示例

    我来为你详细讲解Java使用代码模拟高并发操作的示例攻略。 1. 实现思路 高并发是指在同一时间内有大量的请求涌入到系统中,如何处理这些请求成为一个挑战。使用代码模拟高并发操作,则可以帮助我们评估系统在高并发情况下的稳定性和可靠性。实现思路如下: 定义一个接口或者方法,并为该方法添加synchronized关键字,确保该方法同一时间只能被一个线程访问,以模拟…

    多线程 2023年5月16日
    00
  • 分析python并发网络通信模型

    下面我结合示例详细讲解“分析python并发网络通信模型”的完整攻略。 一、了解Python的GIL Python语言自身带有GIL(全局解释器锁)。GIL是一种互斥锁,它保证同时只有一个线程在解释器中被执行,这样也就导致了Python的多线程程序并不能利用多核CPU的优势。 因此,在Python中实现并发多线程需要使用多个进程而不是多个线程,或者使用一些协…

    多线程 2023年5月17日
    00
  • Java基础之多线程的三种实现方式

    Java基础之多线程的三种实现方式 在Java中,通过多线程可以让程序同时执行多个任务,提高程序的并发性。这篇文章将会介绍Java多线程的三种实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口。同时,我们还会附上代码示例进行详细说明。 继承Thread类 第一种实现多线程的方式是继承Thread类。继承Thread类后需要重…

    多线程 2023年5月17日
    00
  • .net中线程同步的典型场景和问题剖析

    针对“.net中线程同步的典型场景和问题剖析”的话题,我来进行详细讲解,包括以下几个部分: 线程同步的概念 线程同步的必要性和作用 线程同步的实现方式 .net中线程同步的典型场景和问题剖析 示例说明 1. 线程同步的概念 线程同步是指在多个线程之间,对共享资源的访问进行协调和管理,以避免竞争条件和死锁等问题。 2. 线程同步的必要性和作用 当多个线程同时访…

    多线程 2023年5月16日
    00
  • Java面试必备之JMM高并发编程详解

    Java面试必备之JMM高并发编程详解攻略 一、JMM是什么? Java内存模型(Java Memory Model,JMM)是Java虚拟机规范中定义的一种计算机内存模型,即Java程序中多线程之间共享变量的访问规则。 Java程序采用多线程技术,为实现高并发效果,需要保证不同线程之间对共享变量的操作可以正确地被其他线程所读取。Java内存模型规定了Jav…

    多线程 2023年5月16日
    00
  • JAVA如何解决并发问题

    为了解决并发问题,Java提供了以下解决方法: 同步方法(Synchronized Methods) 同步方法可以解决多线程访问共享数据时的并发问题。同步方法在方法签名中使用synchronized关键字来标记,使得该方法在同一时间只能被一个线程执行。当一个线程执行同步方法时,其他线程无法访问该方法,直到该线程完成对共享数据的操作并退出该方法。 示例1: p…

    多线程 2023年5月16日
    00
  • Golang极简入门教程(三):并发支持

    Golang极简入门教程(三):并发支持 什么是并发 并发是多个任务在同一时间间隔内同时执行的能力。在计算机中,使用线程和进程实现并发。 多线程和多进程 在计算机中,我们可以同时使用多线程和多进程来实现并发。 多线程: 操作系统会创建多个线程,每个线程可以执行不同的任务,这些任务会同时运行。这样可以提高程序的性能,避免单线程运行的资源浪费问题。同时,线程之间…

    多线程 2023年5月17日
    00
  • 详解Python并发编程之创建多线程的几种方法

    让我详细讲解一下“详解Python并发编程之创建多线程的几种方法”的完整攻略。 1. 为什么要使用多线程 在Python中使用多线程可以让一台计算机同时执行多个任务,从而提高程序的运行效率。具体来说,多线程可以在以下情况下使用: 需要处理大量IO密集型任务,如网络编程、读写文件等操作。 需要执行CPU密集型任务,如计算、图形渲染等操作。 需要同时处理多个任务…

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