当前位置: 首页 > news >正文

线程池(六):ThreadLocal相关知识详解

线程池(六):ThreadLocal相关知识详解

  • 线程池(六):ThreadLocal相关知识详解
    • 一、概述
      • 定义与作用
      • 应用场景
    • 二、ThreadLocal基本使用
      • 创建ThreadLocal对象
      • 设置和获取值
      • 初始化值
      • 完整示例
    • 三、ThreadLocal的实现原理&源码解析
      • 内部结构
      • set方法源码解析
      • get方法源码解析
      • remove方法源码解析
      • 弱引用的作用
    • 四、ThreadLocal内存泄露问题
      • 内存泄露产生原因
      • 如何避免内存泄露

线程池(六):ThreadLocal相关知识详解

一、概述

定义与作用

ThreadLocal是Java中的一个类,它提供了线程本地变量的功能。简单来说,每个使用ThreadLocal的线程都拥有自己独立的变量副本,各个线程之间的变量副本相互隔离,互不干扰。这就解决了多线程环境下变量共享的冲突问题。

在多线程编程中,当多个线程同时访问一个共享变量时,可能会出现数据不一致等并发问题。而ThreadLocal可以让每个线程都有自己专属的变量,就好像每个线程都“私藏”了一份变量,各自使用各自的,从根本上避免了线程间对共享变量的竞争。

应用场景

  1. 数据库连接:在一个Web应用中,每个请求通常由一个线程来处理。为了避免多个线程之间数据库连接的混乱,我们可以使用ThreadLocal来为每个线程保存一个独立的数据库连接。这样每个线程在处理请求过程中,使用的都是自己的数据库连接,不会相互干扰。
  2. 用户会话信息:在Web开发中,需要记录当前用户的会话信息,比如用户的登录状态、用户ID等。使用ThreadLocal可以方便地在一个线程处理的整个流程中随时获取和设置这些会话信息,并且不同用户的请求线程之间的会话信息不会混淆。
  3. 事务管理:在涉及事务的操作中,确保一个事务内的一系列操作都在同一个数据库连接上进行是很重要的。通过ThreadLocal可以将事务相关的数据库连接绑定到当前线程,在事务的各个操作环节中,都能使用到同一个连接,保证事务的一致性。

二、ThreadLocal基本使用

创建ThreadLocal对象

创建ThreadLocal对象非常简单,只需要使用new关键字即可。示例代码如下:

ThreadLocal<String> threadLocal = new ThreadLocal<>();

这里创建了一个ThreadLocal对象,它存储的是String类型的变量。

设置和获取值

  1. 设置值:通过set方法可以为当前线程设置ThreadLocal变量的值。示例如下:
threadLocal.set("Hello, ThreadLocal!");

这行代码会将字符串"Hello, ThreadLocal!"设置为当前线程对应的ThreadLocal变量的值。

  1. 获取值:使用get方法可以获取当前线程中ThreadLocal变量的值。示例如下:
String value = threadLocal.get();
System.out.println(value);

如果当前线程还没有设置过值,get方法会返回null

初始化值

有时候我们希望在ThreadLocal被创建时就有一个初始值,而不是等到第一次set操作。可以通过继承ThreadLocal并重写initialValue方法来实现。示例代码如下:

ThreadLocal<String> initializedThreadLocal = new ThreadLocal<>() {@Overrideprotected String initialValue() {return "Initial value";}
};
String initialValue = initializedThreadLocal.get();
System.out.println(initialValue);  // 输出:Initial value

或者使用Java 8引入的withInitial方法:

ThreadLocal<String> anotherThreadLocal = ThreadLocal.withInitial(() -> "Another initial value");
String anotherInitValue = anotherThreadLocal.get();
System.out.println(anotherInitValue);  // 输出:Another initial value

完整示例

下面是一个完整的示例,展示了多个线程使用ThreadLocal的情况:

public class ThreadLocalExample {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Thread thread1 = new Thread(() -> {threadLocal.set(10);System.out.println("Thread1: " + threadLocal.get());});Thread thread2 = new Thread(() -> {threadLocal.set(20);System.out.println("Thread2: " + threadLocal.get());});thread1.start();thread2.start();}
}

在这个示例中,thread1thread2两个线程分别设置并获取自己线程内ThreadLocal变量的值,它们之间互不影响。

三、ThreadLocal的实现原理&源码解析

内部结构

  1. Thread类中的ThreadLocalMap:在Thread类中,有一个名为threadLocals的成员变量,它的类型是ThreadLocal.ThreadLocalMap。这是一个专门为ThreadLocal设计的定制化的哈希映射表。每个线程都有自己独立的ThreadLocalMap,用于存储该线程中所有ThreadLocal变量及其对应的值。
  2. ThreadLocalMap的EntryThreadLocalMap内部使用Entry数组来存储数据。EntryThreadLocalMap的静态内部类,它继承自WeakReference<ThreadLocal<?>>。每个Entry对象包含一个对ThreadLocal对象的弱引用(作为键)和对应的值。

set方法源码解析

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
  1. 首先获取当前线程Thread t = Thread.currentThread();,这是因为ThreadLocal变量是与线程绑定的,每个线程都有自己的副本。
  2. 然后通过getMap(t)方法获取当前线程的ThreadLocalMapgetMap方法的实现如下:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

它直接返回当前线程的threadLocals成员变量,即该线程的ThreadLocalMap
3. 如果ThreadLocalMap不为null,则调用map.set(this, value)将当前ThreadLocal对象(this)作为键,传入的值value作为值,存入ThreadLocalMap中。map.set方法的实现较为复杂,主要是处理哈希冲突等情况,大致思路是通过计算ThreadLocal对象的哈希值,找到对应的数组索引位置,如果该位置已经有元素(发生哈希冲突),则通过线性探测等方式寻找下一个可用的位置来存储。
4. 如果当前线程的ThreadLocalMapnull,则调用createMap(t, value)方法创建一个新的ThreadLocalMap,并将当前ThreadLocal对象和值存入其中。createMap方法的实现如下:

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

它创建了一个新的ThreadLocalMap对象,并将其赋值给当前线程的threadLocals成员变量。

get方法源码解析

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();
}
  1. 同样先获取当前线程Thread t = Thread.currentThread();,再获取当前线程的ThreadLocalMap
  2. 如果ThreadLocalMap不为null,则通过map.getEntry(this)方法获取与当前ThreadLocal对象对应的EntrygetEntry方法主要是根据ThreadLocal对象的哈希值在Entry数组中查找对应的元素。
  3. 如果找到了对应的Entry,则将其中存储的值转换为相应类型并返回。
  4. 如果没有找到对应的Entry(可能是因为还没有设置过值等原因),则调用setInitialValue方法。setInitialValue方法的实现如下:
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}

它先调用initialValue方法获取初始值(如果之前重写过initialValue方法),然后将这个初始值存入当前线程的ThreadLocalMap中,并返回该初始值。

remove方法源码解析

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
  1. 先获取当前线程的ThreadLocalMap
  2. 如果ThreadLocalMap不为null,则调用m.remove(this)方法从ThreadLocalMap中移除与当前ThreadLocal对象对应的键值对。remove方法会根据ThreadLocal对象的哈希值找到对应的Entry,并将其从数组中移除,同时还会处理一些相关的清理和调整工作,以保证ThreadLocalMap的正常结构和性能。

弱引用的作用

ThreadLocalMap中的Entry继承自WeakReference<ThreadLocal<?>>,这意味着ThreadLocal对象作为键是被弱引用的。当没有其他强引用指向ThreadLocal对象时,在下次垃圾回收时,这个ThreadLocal对象就会被回收。这样设计的好处是可以避免内存泄漏。例如,当一个ThreadLocal对象不再被使用(没有强引用指向它),如果不是弱引用,那么即使线程结束了,ThreadLocalMap中仍然会持有这个ThreadLocal对象的引用,导致该对象无法被回收,从而造成内存泄漏。而使用弱引用,在合适的时候可以让ThreadLocal对象被垃圾回收器回收,减少内存占用。

四、ThreadLocal内存泄露问题

内存泄露产生原因

虽然ThreadLocalMap中对ThreadLocal对象采用了弱引用,在一定程度上可以避免内存泄漏,但如果使用不当,仍然可能会出现内存泄漏问题。主要原因在于Entry中的值(value)是强引用。当ThreadLocal对象被垃圾回收后,Entry中对应的键(ThreadLocal对象的弱引用)变为null,但值(value)仍然存在于ThreadLocalMap中,如果后续没有对这些无效的Entry进行清理,那么这些值就会一直占用内存,导致内存泄漏。

例如,在线程池场景中,线程是复用的。如果一个线程使用了ThreadLocal,并设置了值,当这个线程执行完任务被放回线程池等待下一次任务时,即使ThreadLocal对象本身已经没有强引用(可以被垃圾回收),但线程的ThreadLocalMap中仍然保留着之前设置的值的强引用。如果不进行清理,随着线程池不断复用线程,这些无效的值就会不断累积,占用越来越多的内存。

如何避免内存泄露

  1. 手动调用remove方法:在使用完ThreadLocal变量后,及时调用remove方法。例如在Web应用中,在一个请求处理完成后,在相关的拦截器或者业务代码中调用ThreadLocalremove方法,将当前线程中ThreadLocal变量对应的值从ThreadLocalMap中移除。示例代码如下:
public class MemoryLeakAvoidanceExample {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Thread thread = new Thread(() -> {try {threadLocal.set(10);// 模拟业务逻辑处理// ...} finally {threadLocal.remove();}});thread.start();}
}

在这个示例中,使用finally块确保无论业务逻辑是否正常执行完毕,都会调用remove方法,及时清理ThreadLocal变量对应的值。
2. 使用try - finally代码块:在使用ThreadLocal的代码块中,尽量使用try - finally结构。在try块中进行正常的业务操作,包括设置和获取ThreadLocal变量的值,在finally块中调用remove方法。这样可以保证在任何情况下,即使出现异常,也能正确地清理ThreadLocal变量,避免内存泄漏。
3. 在线程池场景中的特殊处理:对于线程池场景,由于线程会被复用,更需要注意ThreadLocal的清理。可以自定义线程池,在任务执行前设置ThreadLocal变量,在任务执行完成后,通过线程池的钩子方法(如afterExecute方法)来调用ThreadLocalremove方法,确保每次任务执行完毕后都能清理相关的ThreadLocal变量。以下是一个简单的自定义线程池示例:

import java.util.concurrent.*;public class CustomThreadPoolExecutor extends ThreadPoolExecutor {public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void afterExecute(Runnable r, Throwable t) {// 假设这里有一个全局的ThreadLocal变量需要清理ThreadLocal<Integer> threadLocal = new ThreadLocal<>();threadLocal.remove();super.afterExecute(r, t);}
}

通过这种方式,可以在多线程复用的场景下有效地避免ThreadLocal导致的内存泄漏问题。

综上所述,ThreadLocal在多线程编程中是一个非常有用的工具,通过合理使用它可以方便地实现线程本地变量的管理,但同时也需要注意其实现原理和可能出现的内存泄漏问题,通过正确的使用方式来确保程序的性能和稳定性。

相关文章:

  • 移除元素(简单)
  • 游戏引擎学习第246天:将 Worker 上下文移到主线程创建
  • C语言中结构体的字节对齐的应用
  • WPF与C++ 动态库交互
  • 【网络安全】用 Linux 命令行 CLI 日志文件处理指南
  • 在springboot项目中,如何进行excel表格的导入导出功能?
  • 从OpenAI收购实时数据引擎揭示AI数据库进化方向
  • django之优化分页功能(利用参数共存及封装来实现)
  • 【Linux】Centos7 安装 Docker 详细教程
  • 5.3/Q1,GBD数据库最新文章解读
  • MySQL多查询条件下深度分页性能优化技巧及示例总结
  • 【Castle-X机器人】一、模块安装与调试:机器人底盘
  • JavaScript 笔记 --- part6 --- JS进阶 (part1)
  • 高性能电脑系统优化工具Advanced SystemCare PRO v18.3.0.240 解锁永久专业版
  • 华为云loT物联网介绍与使用
  • 【Castle-X机器人】五、物联网模块配置与调试
  • 4.26学习——web刷题
  • Vue3中AbortController取消请求的用法详解
  • 模态链:利用视觉-语言模型从多模态人类视频中学习操作程序
  • 计算机网络 | Chapter1 计算机网络和因特网
  • 专业竞演、剧场LIVE直播,32位越剧新星逐梦上海
  • 人民时评:投资于人,促高质量充分就业
  • 生于1984年,马玥已任辽宁锦州北镇市代市长
  • 中华人民共和国和肯尼亚共和国关于打造新时代全天候中非命运共同体典范的联合声明
  • 中国和阿塞拜疆签署互免签证协定
  • 驯服象牙塔:美国政府对大学的战争是一场善恶对抗吗