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>

目录
相关文章
|
2月前
|
安全 Java
深入拆解 ReentrantLock:从底层实现到生产最佳实践
本文深入剖析ReentrantLock底层原理,基于AQS框架详解state状态、CLH队列及公平/非公平锁机制;对比synchronized在实现、功能(可中断、多条件变量)和性能上的差异;结合代码演示三类锁适用场景与最佳实践,助你写出高效、健壮的并发程序。
324 4
|
3月前
|
SQL 关系型数据库 Java
吃透 Seata 分布式事务:原理拆解 + 生产级落地 + 全场景避坑实战
本文深度解析阿里开源分布式事务框架Seata:剖析TC/TM/RM三大角色与全局事务流程,详解AT(零侵入)、TCC(强控制)、SAGA(长事务)、XA(强一致)四大模式原理、适用场景及核心对比,并通过电商下单实战演示AT模式落地,最后系统梳理生产环境高可用、SQL限制、幂等处理、XID传播等全链路避坑指南。
861 4
|
缓存 Java Android开发
【OOM异常排查经验】
【OOM异常排查经验】
1059 0
|
存储 缓存 关系型数据库
【MySQL进阶-08】深入理解innodb存储格式,双写机制,buffer pool底层结构和淘汰策略
【MySQL进阶-08】深入理解innodb存储格式,双写机制,buffer pool底层结构和淘汰策略
1589 0
|
3月前
|
存储 Prometheus 前端开发
Grafana+Loki+Alloy构建企业级日志平台
Loki是一个水平可扩展、高可用的多租户日志聚合系统,其设计灵感来自Prometheus。与Prometheus不同,Loki专注于日志处理,采用推送方式收集日志,并通过标签索引而非日志内容实现高效查询。其架构包含Distributor、Ingester和Querier等组件,分别负责请求分发、日志存储和查询处理。Loki将日志数据压缩存储在对象存储中,大大降低了成本。部署时,可结合Grafana Alloy作为日志收集器,并通过Grafana可视化界面或LogQL查询语言进行日志检索和分析。系统支持多种查
|
消息中间件 网络协议 前端开发
殷浩详解DDD:如何避免写流水账代码?
在日常工作中我观察到,面对老系统重构和迁移场景,有大量代码属于流水账代码,通常能看到开发在对外的API接口里直接写业务逻辑代码,或者在一个服务里大量的堆接口,导致业务逻辑实际无法收敛,接口复用性比较差。所以本文主要想系统性的解释一下如何通过DDD的重构,将原有的流水账代码改造为逻辑清晰、职责分明的模块。
殷浩详解DDD:如何避免写流水账代码?
|
8月前
|
人工智能 供应链 决策智能
智能体来了:阿里云×黎跃春助力企业降本增效的实战案例
智能体正引领企业迈向智能化新时代。阿里云联合AI专家黎跃春推出“智能体赋能企业创新计划”,助力企业实现降本增效与智能决策。通过“三步法”落地智能体,推动人机协同,重构竞争力。
|
2月前
|
人工智能 Linux API
【AI龙虾🦞OpenClaw保姆级手册】Skills详细解读+本地与阿里云部署方法+百炼API配置及常见问题处理
Skills作为AI生态中提升生产力的核心工具,已经火遍技术圈数月,全网都在热议其对工作效率的颠覆式提升,但真正能把Skills用透、并结合开源AI工具实现本地化落地的人却不多。与其追逐热点盲目尝试,不如等技术成熟后一次性吃透核心逻辑与实操方法——这也是我翻遍50余篇行业文章,结合Gemini、Claude、GPT等工具深度研究后,总结出的核心思路。本文不仅会讲透Skills的底层逻辑、创建与使用方法,还会附上2026年新手零基础就能上手的OpenClaw(Clawdbot)阿里云、MacOS、Linux、Windows11全平台部署流程,以及阿里云百炼API的配置方法和常见问题解答
756 0
|
6月前
|
监控 Java 测试技术
OOM排查之路:一次曲折的线上故障复盘
本文记录了一次线上服务因Paimon数据湖与RocksDB集成引发的三次内存溢出(OOM)故障排查全过程。通过MAT、NMT、async-profiler等工具,结合监控分析与专家协作,最终定位到RocksDB通过JNI申请的堆外内存未释放是根源。团队通过架构优化,改由Flink统一写入Paimon,彻底解决问题。文章系统梳理了排查思路与工具使用,为类似技术栈提供宝贵经验。