ThreadLocal 90% 开发者都踩过的坑:底层原理拆解、内存泄漏根治与架构级用法全解

简介: 本文深度解析ThreadLocal底层原理(JDK17源码级),直击多线程上下文串号、线程池OOM、traceId传递失效等高频痛点,详解内存泄漏本质(弱引用实为兜底优化)、根治方案(强制remove)及TTL等生产级实践,涵盖用户/trace/事务/数据源四大上下文场景与七大避坑指南。

你是否遇到过这些问题:多线程环境下用户上下文莫名串号、线程池集成ThreadLocal后服务运行久了出现OOM、父子线程传递traceId偶发失效、面试被问底层原理只能说出“线程私有变量”便卡壳?本文将从底层源码到生产实践,全链路拆解ThreadLocal的核心逻辑,根治所有常见坑点。

一、ThreadLocal核心认知

ThreadLocal是JDK提供的线程隔离级别的变量存储工具,它的核心设计思想是“空间换时间”,为每个线程创建独立的变量副本,每个线程只能访问和修改自己副本中的值,从根本上避免了多线程共享变量的竞争问题,同时解决了业务上下文跨方法层层传递的冗余问题。

它的核心价值体现在两个场景:

  1. 线程安全:变量在线程间隔离,无锁竞争,天然避免并发安全问题
  2. 上下文传递:用户信息、traceId、事务状态等上下文,无需在方法参数中层层传递,可在全链路任意位置获取

二、JDK17底层原理全拆解

2.1 核心数据结构关系

ThreadLocal的核心实现并非在自身类中存储变量,而是依托于Thread线程类的内部存储结构,三者的关系架构如下:

从架构图可以明确三个核心结论:

  1. 每个Thread线程对象中,都持有两个ThreadLocal.ThreadLocalMap类型的成员变量:threadLocalsinheritableThreadLocals,默认值为null
  2. ThreadLocal本身不存储变量值,它只是作为key,通过自身的hash值定位到当前线程ThreadLocalMap中的对应Entry,从而获取value
  3. 变量副本真正存储在每个线程自己的ThreadLocalMap中,线程之间完全隔离,互不可见

2.2 ThreadLocalMap核心实现

ThreadLocalMap是ThreadLocal的静态内部类,是一个定制化的哈希表,专为ThreadLocal场景设计,核心结构如下:

  1. Entry实体:继承自WeakReference<ThreadLocal<?>>,key为当前ThreadLocal实例的弱引用,value为线程私有变量的强引用
  2. 哈希冲突解决:采用线性探测法,而非HashMap的拉链法,哈希值计算采用nextHashCode增量算法,减少哈希冲突
  3. 过期清理机制:内置了针对key为null的过期Entry的清理逻辑,在get/set/remove操作时会触发,降低内存泄漏风险

2.3 核心方法执行流程

2.3.1 set()方法执行流程

set()方法用于为当前线程设置ThreadLocal变量副本,执行流程如下:

核心源码逻辑(JDK17)精简如下:

public void set(T value) {
   Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null) {
       map.set(this, value);
   } else {
       createMap(t, value);
   }
}

ThreadLocalMap getMap(Thread t) {
   return t.threadLocals;
}

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

2.3.2 get()方法执行流程

get()方法用于获取当前线程中ThreadLocal对应的变量副本,核心逻辑为:

  1. 获取当前线程的ThreadLocalMap
  2. 以当前ThreadLocal为key,从Map中获取对应的Entry
  3. 若Entry存在,返回对应的value
  4. 若Map不存在或Entry不存在,调用setInitialValue()方法初始化初始值并返回

2.3.3 remove()方法执行流程

remove()方法是ThreadLocal正确使用的核心,它会从当前线程的ThreadLocalMap中,移除当前ThreadLocal对应的Entry,同时触发过期Entry的清理,从根本上避免脏数据和内存泄漏。

三、内存泄漏的本质与根治方案

3.1 内存泄漏的核心误区纠正

90%的开发者都存在一个错误认知:“弱引用是ThreadLocal内存泄漏的元凶”。这个结论完全颠倒了因果,弱引用不仅不是泄漏的原因,反而是JDK为了降低泄漏风险做的兜底优化。

我们先明确Java引用的核心特性:

  • 强引用:普通的对象引用,只要强引用存在,GC永远不会回收被引用的对象
  • 弱引用:生命周期仅存活到下一次GC之前,无论内存是否充足,GC触发时都会回收被弱引用关联的对象

3.2 内存泄漏的触发原理

ThreadLocal内存泄漏的完整触发流程如下:

从流程中可以明确,内存泄漏的两个必要条件

  1. 线程生命周期过长:核心线程池的线程生命周期与JVM一致,线程不会终止,ThreadLocalMap不会被整体回收
  2. 过期Entry未被清理:ThreadLocal外部强引用被回收后,key变为null,后续没有任何ThreadLocal的操作触发清理逻辑,导致value一直被Entry强引用,无法被GC回收

3.3 为什么说弱引用是兜底优化?

假设Entry的key使用强引用,会发生什么? 即使ThreadLocal的外部强引用被释放,Entry的key依然持有ThreadLocal的强引用,ThreadLocal实例永远不会被GC回收,连key都无法被标记为过期,整个Entry永远不会被清理,内存泄漏会比现在严重得多。

而弱引用的设计,让ThreadLocal实例在外部强引用消失后,能被GC正常回收,key变为null,为后续的清理逻辑提供了触发条件,是JDK提供的一层兜底保障。

3.4 内存泄漏的根治方案

根治内存泄漏只有一个强制标准:每次使用完ThreadLocal,必须在finally块中手动调用remove()方法。

这个操作会直接从当前线程的ThreadLocalMap中移除对应的Entry,彻底释放key和value的引用,无论线程生命周期多长,都不会出现内存泄漏。同时,这个操作也能彻底避免线程池线程复用导致的脏数据问题。

四、架构级正确用法实战

ThreadLocal的架构级用法,核心是封装上下文持有者,实现业务上下文的全链路透明传递,同时严格遵守使用规范,避免生产问题。以下是4个生产环境高频使用的落地示例。

4.1 用户上下文持有者

用户上下文是Web项目中最高频的使用场景,在网关/拦截器中解析用户信息存入ThreadLocal,业务代码中任意位置可直接获取,请求结束后自动清理。

package com.jam.demo.context;

import com.jam.demo.model.UserInfo;
import org.springframework.util.ObjectUtils;

/**
* 用户上下文持有者
*
* @author ken
*/

public final class UserContextHolder {

   private UserContextHolder() {
   }

   private static final ThreadLocal<UserInfo> USER_THREAD_LOCAL = new ThreadLocal<>();

   /**
    * 设置当前线程的用户信息
    *
    * @param userInfo 用户信息
    */

   public static void set(UserInfo userInfo) {
       if (!ObjectUtils.isEmpty(userInfo)) {
           USER_THREAD_LOCAL.set(userInfo);
       }
   }

   /**
    * 获取当前线程的用户信息
    *
    * @return 用户信息
    */

   public static UserInfo get() {
       return USER_THREAD_LOCAL.get();
   }

   /**
    * 获取当前登录用户ID
    *
    * @return 用户ID
    */

   public static Long getUserId() {
       UserInfo userInfo = get();
       return ObjectUtils.isEmpty(userInfo) ? null : userInfo.getUserId();
   }

   /**
    * 清除当前线程的用户信息
    */

   public static void remove() {
       USER_THREAD_LOCAL.remove();
   }
}

对应的拦截器实现,确保请求结束后自动清理:

package com.jam.demo.interceptor;

import com.jam.demo.context.UserContextHolder;
import com.jam.demo.model.UserInfo;
import com.jam.demo.utils.JwtUtils;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

/**
* 用户上下文拦截器
*
* @author ken
*/

@Slf4j
@Component
public class UserContextInterceptor implements HandlerInterceptor {

   private static final String AUTHORIZATION_HEADER = "Authorization";
   private static final String BEARER_PREFIX = "Bearer ";

   @Override
   @Operation(hidden = true)
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
       String token = request.getHeader(AUTHORIZATION_HEADER);
       if (StringUtils.hasText(token) && token.startsWith(BEARER_PREFIX)) {
           String realToken = token.substring(BEARER_PREFIX.length());
           UserInfo userInfo = JwtUtils.parseToken(realToken);
           UserContextHolder.set(userInfo);
       }
       return true;
   }

   @Override
   @Operation(hidden = true)
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
       UserContextHolder.remove();
   }
}

4.2 全链路追踪traceId上下文

全链路追踪是微服务架构的核心能力,通过ThreadLocal存储traceId,实现全链路日志的串联,定位问题时可通过traceId快速检索全链路日志。

package com.jam.demo.context;

import org.springframework.util.StringUtils;

import java.util.UUID;

/**
* 链路追踪上下文持有者
*
* @author ken
*/

public final class TraceContextHolder {

   private TraceContextHolder() {
   }

   private static final ThreadLocal<String> TRACE_ID_TL = ThreadLocal.withInitial(() -> UUID.randomUUID().toString().replace("-", ""));

   /**
    * 设置当前链路的traceId
    *
    * @param traceId 链路ID
    */

   public static void set(String traceId) {
       if (StringUtils.hasText(traceId)) {
           TRACE_ID_TL.set(traceId);
       }
   }

   /**
    * 获取当前链路的traceId
    *
    * @return 链路ID
    */

   public static String get() {
       return TRACE_ID_TL.get();
   }

   /**
    * 清除当前链路的traceId
    */

   public static void remove() {
       TRACE_ID_TL.remove();
   }
}

4.3 编程式事务上下文管理

编程式事务相比声明式事务,具备更灵活的事务控制能力,通过ThreadLocal存储事务状态,可实现嵌套方法的事务状态共享与统一控制。

package com.jam.demo.context;

import org.springframework.transaction.TransactionStatus;
import org.springframework.util.ObjectUtils;

/**
* 事务上下文持有者
*
* @author ken
*/

public final class TransactionContextHolder {

   private TransactionContextHolder() {
   }

   private static final ThreadLocal<TransactionStatus> TRANSACTION_TL = new ThreadLocal<>();

   /**
    * 设置当前线程的事务状态
    *
    * @param transactionStatus 事务状态
    */

   public static void set(TransactionStatus transactionStatus) {
       if (!ObjectUtils.isEmpty(transactionStatus)) {
           TRANSACTION_TL.set(transactionStatus);
       }
   }

   /**
    * 获取当前线程的事务状态
    *
    * @return 事务状态
    */

   public static TransactionStatus get() {
       return TRANSACTION_TL.get();
   }

   /**
    * 清除当前线程的事务状态
    */

   public static void remove() {
       TRANSACTION_TL.remove();
   }
}

对应的业务使用示例:

package com.jam.demo.service;

import com.jam.demo.context.TransactionContextHolder;
import com.jam.demo.entity.Order;
import com.jam.demo.mapper.OrderMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

/**
* 订单服务
*
* @author ken
*/

@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {

   private final PlatformTransactionManager transactionManager;
   private final TransactionDefinition transactionDefinition;
   private final OrderMapper orderMapper;
   private final StockService stockService;

   /**
    * 创建订单
    *
    * @param order 订单信息
    * @return 订单ID
    */

   public Long createOrder(Order order) {
       TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
       try {
           TransactionContextHolder.set(status);
           stockService.deductStock(order.getProductId(), order.getQuantity());
           orderMapper.insert(order);
           transactionManager.commit(status);
           return order.getId();
       } catch (Exception e) {
           transactionManager.rollback(status);
           log.error("创建订单失败,事务已回滚", e);
           throw new RuntimeException("创建订单失败", e);
       } finally {
           TransactionContextHolder.remove();
       }
   }
}

4.4 动态数据源切换上下文

多数据源场景下,通过ThreadLocal存储当前线程使用的数据源key,实现动态数据源的切换,满足读写分离、分库分表等业务需求。

package com.jam.demo.context;

import org.springframework.util.StringUtils;

/**
* 动态数据源上下文持有者
*
* @author ken
*/

public final class DynamicDataSourceContextHolder {

   private DynamicDataSourceContextHolder() {
   }

   private static final ThreadLocal<String> DATA_SOURCE_KEY_TL = new ThreadLocal<>();

   /**
    * 设置当前线程使用的数据源key
    *
    * @param dataSourceKey 数据源key
    */

   public static void set(String dataSourceKey) {
       if (StringUtils.hasText(dataSourceKey)) {
           DATA_SOURCE_KEY_TL.set(dataSourceKey);
       }
   }

   /**
    * 获取当前线程使用的数据源key
    *
    * @return 数据源key
    */

   public static String get() {
       return DATA_SOURCE_KEY_TL.get();
   }

   /**
    * 清除当前线程的数据源key,恢复默认数据源
    */

   public static void remove() {
       DATA_SOURCE_KEY_TL.remove();
   }
}

五、全场景避坑指南

5.1 线程池复用导致的脏数据问题

问题本质

线程池的核心是线程复用,线程不会随着任务执行结束而销毁,而是会继续处理下一个任务。如果上一个任务set了ThreadLocal的值,没有调用remove(),下一个任务复用同一个线程时,会拿到上一个任务遗留的值,造成脏数据,甚至引发业务逻辑错误。

错误示例

package com.jam.demo.badcase;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* ThreadLocal线程池脏数据错误示例
*
* @author ken
*/

@Slf4j
public class ThreadLocalDirtyDataBadCase {

   private static final ThreadLocal<Integer> COUNT_TL = ThreadLocal.withInitial(() -> 0);
   private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1);

   public static void main(String[] args) {
       EXECUTOR.submit(() -> {
           COUNT_TL.set(100);
           log.info("第一个任务获取值:{}", COUNT_TL.get());
       });

       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           log.error("线程中断", e);
       }

       EXECUTOR.submit(() -> {
           log.info("第二个任务获取值:{}", COUNT_TL.get());
       });

       EXECUTOR.shutdown();
   }
}

执行结果:第二个任务预期输出0,实际输出100,脏数据产生。

避坑方案

无论任务是否执行成功,必须在finally块中调用remove()方法,确保任务执行结束后,清理当前线程的ThreadLocal值。

package com.jam.demo.goodcase;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* ThreadLocal线程池正确使用示例
*
* @author ken
*/

@Slf4j
public class ThreadLocalCorrectUsageCase {

   private static final ThreadLocal<Integer> COUNT_TL = ThreadLocal.withInitial(() -> 0);
   private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1);

   public static void main(String[] args) {
       EXECUTOR.submit(() -> {
           try {
               COUNT_TL.set(100);
               log.info("第一个任务获取值:{}", COUNT_TL.get());
           } finally {
               COUNT_TL.remove();
           }
       });

       try {
           Thread.sleep(1000);
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
           log.error("线程中断", e);
       }

       EXECUTOR.submit(() -> {
           try {
               log.info("第二个任务获取值:{}", COUNT_TL.get());
           } finally {
               COUNT_TL.remove();
           }
       });

       EXECUTOR.shutdown();
   }
}

5.2 父子线程上下文传递失效问题

问题本质

JDK提供的InheritableThreadLocal可以实现父子线程的上下文传递,原理是子线程初始化时,会复制父线程的inheritableThreadLocals到自己的存储空间中。但在线程池场景下,核心线程是提前创建并复用的,不会每次提交任务都重新初始化,导致父线程的上下文更新后,子线程无法拿到最新的值,上下文传递失效。

错误示例

package com.jam.demo.badcase;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* InheritableThreadLocal线程池传值失效错误示例
*
* @author ken
*/

@Slf4j
public class InheritableThreadLocalBadCase {

   private static final InheritableThreadLocal<String> TRACE_ID_TL = new InheritableThreadLocal<>();
   private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(1);

   public static void main(String[] args) throws InterruptedException {
       EXECUTOR.submit(() -> log.info("核心线程初始化完成")).get();

       TRACE_ID_TL.set("trace-001");
       log.info("父线程traceId:{}", TRACE_ID_TL.get());
       EXECUTOR.submit(() -> log.info("子线程第一次获取traceId:{}", TRACE_ID_TL.get()));
       Thread.sleep(1000);

       TRACE_ID_TL.set("trace-002");
       log.info("父线程新traceId:{}", TRACE_ID_TL.get());
       EXECUTOR.submit(() -> log.info("子线程第二次获取traceId:{}", TRACE_ID_TL.get()));

       EXECUTOR.shutdown();
   }
}

执行结果:子线程第二次预期输出trace-002,实际输出trace-001,传值失效。

避坑方案

使用阿里开源的TransmittableThreadLocal(TTL),它在InheritableThreadLocal的基础上,实现了线程池场景下的上下文传递,每次提交任务时都会复制父线程的最新上下文,任务执行结束后自动清理。

package com.jam.demo.goodcase;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.threadpool.TtlExecutors;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* TransmittableThreadLocal线程池传值正确示例
*
* @author ken
*/

@Slf4j
public class TtlThreadLocalCorrectCase {

   private static final TransmittableThreadLocal<String> TRACE_ID_TL = new TransmittableThreadLocal<>();
   private static final ExecutorService ORIGIN_EXECUTOR = Executors.newFixedThreadPool(1);
   private static final ExecutorService TTL_EXECUTOR = TtlExecutors.getTtlExecutorService(ORIGIN_EXECUTOR);

   public static void main(String[] args) throws InterruptedException {
       TTL_EXECUTOR.submit(() -> log.info("核心线程初始化完成")).get();

       TRACE_ID_TL.set("trace-001");
       log.info("父线程traceId:{}", TRACE_ID_TL.get());
       TTL_EXECUTOR.submit(() -> log.info("子线程第一次获取traceId:{}", TRACE_ID_TL.get()));
       Thread.sleep(1000);

       TRACE_ID_TL.set("trace-002");
       log.info("父线程新traceId:{}", TRACE_ID_TL.get());
       TTL_EXECUTOR.submit(() -> log.info("子线程第二次获取traceId:{}", TRACE_ID_TL.get()));

       TRACE_ID_TL.remove();
       TTL_EXECUTOR.shutdown();
   }
}

5.3 ThreadLocal实例创建不当的性能坑

问题本质

很多开发者会将ThreadLocal声明为非静态变量,每次创建业务对象时,都会生成一个新的ThreadLocal实例,导致每个线程的ThreadLocalMap中存在大量的Entry,不仅浪费内存,还会加剧哈希冲突,线性探测法的寻址时间大幅增加,严重影响性能。

避坑方案

ThreadLocal必须声明为private static final,全局唯一实例,避免重复创建。static修饰确保类加载时初始化一次,final修饰避免引用被修改,从根本上避免实例重复创建的问题。

5.4 共享对象存储的并发安全坑

问题本质

很多开发者误以为,只要把对象存入ThreadLocal,就一定是线程安全的。这个认知存在严重漏洞:如果ThreadLocal中存储的是同一个共享对象的引用,即使每个线程都有这个引用的副本,指向的还是堆中的同一个对象,多线程修改这个对象时,依然会存在并发安全问题。

避坑方案

ThreadLocal中尽量存储不可变对象,若必须存储可变对象,确保每个线程存储的是独立的对象副本,而非共享对象的引用。

六、易混淆技术点明确区分

特性 ThreadLocal Synchronized Volatile
核心思想 线程隔离,每个线程拥有独立副本,变量不共享 线程同步,多线程共享同一变量,锁保证串行访问 多线程共享变量,保证可见性与有序性
解决问题 变量隔离存储,避免参数层层传递 共享变量的并发安全,保证三大特性 共享变量的线程可见性,禁止指令重排
原子性保证 不保证原子性,仅保证副本隔离 保证原子性 不保证原子性
性能表现 无锁竞争,高并发下性能优异 存在锁竞争,高并发下有性能损耗 无锁,性能优于锁机制
适用场景 用户上下文、链路追踪、事务上下文 共享变量计数、状态更新、资源竞争 单次读写的状态标记、双重检查锁

七、生产级最佳实践总结

  1. 【强制】ThreadLocal必须声明为private static final,全局唯一实例,避免重复创建导致的内存浪费和性能下降
  2. 【强制】每次使用完ThreadLocal,必须在finally块中手动调用remove()方法,彻底避免脏数据和内存泄漏
  3. 【推荐】初始化ThreadLocal时,使用withInitial()方法设置初始值,避免get()返回null导致空指针异常
  4. 【推荐】父子线程传递上下文时,若使用线程池,必须使用TransmittableThreadLocal,禁止使用InheritableThreadLocal
  5. 【禁止】使用ThreadLocal存储大对象,若必须存储,需确保使用后立即清理
  6. 【禁止】在ThreadLocal中存储共享可变对象,避免出现并发安全问题
  7. 【推荐】封装统一的上下文持有者工具类,禁止业务代码直接操作ThreadLocal的get/set/remove方法,统一管控

ThreadLocal是Java并发编程中的一把利器,只有真正理解了它的底层原理,才能避开所有的坑,在架构设计中发挥它的最大价值,而不是成为生产事故的导火索。

附录:项目依赖配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>3.2.4</version>
       <relativePath/>
   </parent>
   <groupId>com.jam</groupId>
   <artifactId>threadlocal-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>threadlocal-demo</name>
   <description>ThreadLocal Demo Project</description>
   <properties>
       <java.version>17</java.version>
       <mybatis-plus.version>3.5.6</mybatis-plus.version>
       <fastjson2.version>2.0.52</fastjson2.version>
       <guava.version>33.1.0-jre</guava.version>
       <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
       <springdoc.version>2.5.0</springdoc.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springdoc</groupId>
           <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
           <version>${springdoc.version}</version>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>${mybatis-plus.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba.fastjson2</groupId>
           <artifactId>fastjson2</artifactId>
           <version>${fastjson2.version}</version>
       </dependency>
       <dependency>
           <groupId>com.google.guava</groupId>
           <artifactId>guava</artifactId>
           <version>${guava.version}</version>
       </dependency>
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>transmittable-thread-local</artifactId>
           <version>${transmittable-thread-local.version}</version>
       </dependency>
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>1.18.30</version>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>com.mysql</groupId>
           <artifactId>mysql-connector-j</artifactId>
           <scope>runtime</scope>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
           <scope>test</scope>
       </dependency>
   </dependencies>
   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
               <configuration>
                   <excludes>
                       <exclude>
                           <groupId>org.projectlombok</groupId>
                           <artifactId>lombok</artifactId>
                       </exclude>
                   </excludes>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

目录
相关文章
|
Swift iOS开发 开发者
iOS 应用上架流程详解
iOS 应用上架流程详解
|
缓存 Java Android开发
【OOM异常排查经验】
【OOM异常排查经验】
992 0
|
存储 缓存 关系型数据库
【MySQL进阶-08】深入理解innodb存储格式,双写机制,buffer pool底层结构和淘汰策略
【MySQL进阶-08】深入理解innodb存储格式,双写机制,buffer pool底层结构和淘汰策略
1519 0
|
21天前
|
缓存 监控 Java
Java 四大引用体系:从GC回收规则到框架底层实现的完整真相
Java四大引用(强、软、弱、虚)是JDK1.2引入的核心内存管理机制,精准控制对象回收时机。强引用防回收,软引用保缓存(OOM前清理),弱引用防泄漏(GC即回收),虚引用唯一可靠跟踪回收——配合ReferenceQueue实现堆外内存释放等关键兜底。90%开发者仅知皮毛,实为解决OOM、内存泄漏及理解ThreadLocal/NIO底层的基石。(239字)
234 4
|
3月前
|
人工智能 运维 程序员
2026年8个适合程序员逛的在线社区
这是一个开源与共享的时代,编程学习离不开优质社区。GitHub汇聚全球优秀代码,Stack Overflow解决技术难题,InfoQ洞察前沿趋势,CSDN、博客园、51CTO等中文平台助力本土开发者成长。无论是源码学习、问题答疑还是技术交流,这些社区都为程序员提供了丰富资源与成长空间,是提升技能的必备利器。
|
2月前
|
JavaScript 安全 Java
Maven 4 终于来了!5 个最实用的新特性,看这一篇就够了(附超简单示例)
Apache Maven 4.0(2025年底GA)是20年来最大架构升级,非颠覆而是进化:兼容现有pom.xml,无需大改即可享受5大实用新特性——子模块自动发现、父版本自动推断、原生动态版本、消费者POM精简发布、智能构建恢复。仅需JDK 17+,平滑迁移,更简洁、更智能、更可靠!
|
4月前
|
监控 Java 测试技术
OOM排查之路:一次曲折的线上故障复盘
本文记录了一次线上服务因Paimon数据湖与RocksDB集成引发的三次内存溢出(OOM)故障排查全过程。通过MAT、NMT、async-profiler等工具,结合监控分析与专家协作,最终定位到RocksDB通过JNI申请的堆外内存未释放是根源。团队通过架构优化,改由Flink统一写入Paimon,彻底解决问题。文章系统梳理了排查思路与工具使用,为类似技术栈提供宝贵经验。
|
12月前
|
Arthas 监控 Java
Arthas classloader (查看 classloader 的继承树,urls,类加载信息)
Arthas classloader (查看 classloader 的继承树,urls,类加载信息)
275 3
|
9月前
|
设计模式 C++
【实战指南】设计模式 - 工厂模式
工厂模式是一种面向对象设计模式,通过定义“工厂”来创建具体产品实例。它包含简单工厂、工厂方法和抽象工厂三种形式,分别适用于不同复杂度的场景。简单工厂便于理解但扩展性差;工厂方法符合开闭原则,适合单一类型产品创建;抽象工厂支持多类型产品创建,但不便于新增产品种类。三者各有优缺点,适用于不同设计需求。
376 76
|
7月前
|
编解码 网络协议 Java
RPC的三大问题:跨语言、跨平台通信的终极解决方案是如何炼成的?
本文深入解析现代RPC体系的核心挑战与解决方案,涵盖数据表示、传输机制与调用约定,探讨gRPC、HTTP/2、ProtoBuf等技术如何实现高效可靠的跨服务通信,并分析自研RPC协议的设计思路与未来发展路径。
276 8