阳春
Java 后端开发者
6 年经验 · Spring Cloud 微服务架构 · 团队管理

📋 个人简介

6 年 Java 后端开发经验,深耕珠宝 MES/ERP 近三年,主导订单/排产/仓储全链路模块, 服务 40+ 家工厂。2025 年起任项目负责人,带领 4 人团队统筹多模块迭代交付。主导团队 AI 工具从 0 到 1 落地 (Claude Code CLI + Skill/MCP 沉淀团队知识),助力团队效率提升。

💼 工作经历

深圳某珠宝行业 SaaS 公司

Java · 珠宝 MES/ERP 项目组
2023.09 — 至今
  • 2025 年 1 月升任项目负责人,带领 4 人团队统筹多模块迭代交付
  • 主导共享底表拆分:长期累积超 3000 万行,一次 ALTER TABLE 锁表 40 分钟触发全业务阻塞,推动按业务拆分,配合注解增强将核心查询从 40 秒+ 优化至 2~3 秒
  • 设计流式导出组件与统一图片管理服务,消除 OOM 风险
  • 主导团队 AI 工具落地与历史债务治理
  • 系统服务 40+ 家工厂,支撑日均数千条生产记录稳定运行
Spring CloudMySQLRedis Jenkins团队管理AI 辅助开发

厦门某企业信息化公司

Java · 安全生产 / 业务运营业务组
2021.07 — 2023.06
  • 开发负责人,带领 6 人团队(2 Java / 2 .NET / 2 前端)推进需求落地
  • AOP 声明式注解收敛多模块权限校验 + HandlerInterceptor 统一上下文清理
  • 同步通知异步化(RocketMQ),解耦核心业务与通知路径
  • 排查优化慢 SQL,多个核心查询耗时下降约一个数量级
  • MongoDB 接管答题草稿缓存,缓解 MySQL 高并发写入压力
Spring CloudRocketMQMySQL MongoDBRedisAOP SQL 调优团队管理

📝 技术分享

HashMap 底层原理与 JDK 1.8 优化

数据结构:数组 + 链表 + 红黑树。

put 流程:对 key 求 hashCode() → 高 16 位异或低 16 位(扰动函数)→ (n-1) & hash 定位桶位 → 桶为空直接放,不为空判断是链表还是红黑树 → 链表尾插并检查是否 ≥8 转树(数组容量需 ≥64)。

JDK 1.8 优化:优化 头插 → 尾插(避免 resize 时链表死循环);优化 引入红黑树,链表 ≥8 且数组 ≥64 时转树,退化为链表阈值 6;扰动函数从 4 次异或简化为 1 次。

扩容:默认容量 16,负载因子 0.75。扩容时容量翻倍,rehash 利用二进制特性判断高位是 0(留在原位)还是 1(新位置 = 原位置 + 旧容量)。

注意 HashMap 线程不安全,并发 put 可能导致数据丢失或链表死循环(1.7)。并发场景用 ConcurrentHashMap。

ConcurrentHashMap 如何保证线程安全

JDK 1.7:分段锁(Segment),默认 16 段,每段独立 ReentrantLock,并发度 = 段数。put 时定位 Segment → lock() → 头插法。

JDK 1.8 大改:优化 抛弃分段锁,改用 CAS + synchronized 锁桶头节点。数组 + 链表 + 红黑树结构,与 HashMap 一致。

put 流程(1.8):计算 hash → 自旋:若桶为 null 则 CAS 放入 → 若桶头节点 hash==MOVED(-1)则帮忙扩容(helpTransfer)→ 否则 synchronized 锁住桶头节点执行插入。

size 计数:用 CounterCell 数组分摊竞争(类似 LongAdder),避免 CAS 自旋消耗。

与 Hashtable 的区别:Hashtable 锁整个表,put/get 全串行化;ConcurrentHashMap 锁粒度细化到桶级别,读操作不加锁(Node 的 val/next 用 volatile)。

JVM 内存模型与 GC 算法

运行时数据区:堆(对象实例,GC 主战场)、方法区/元空间(类信息、常量、静态变量)、虚拟机栈(栈帧:局部变量表 + 操作数栈 + 动态链接 + 返回地址)、本地方法栈、程序计数器。

堆分代:新生代(Eden + S0 + S1,默认 8:1:1)→ Minor GC 采用复制算法;老年代 → Major/Full GC 采用标记-清除或标记-整理。

GC 算法:标记-清除(碎片化)、标记-整理(移动开销)、复制(浪费空间但快,适合新生代)。现代 JVM 默认 G1:将堆划分为等大 Region,优先回收垃圾最多的 Region(Garbage First),通过 Remembered Set 避免全堆扫描。

GC 触发条件:Eden 满 → Minor GC;老年代空间不足 → Full GC;System.gc() 建议但不保证;元空间不足 → Full GC。

实战 线上 129 万条 Redis List 全量反序列化 → 2.18GB 堆 → Full GC 频繁 → 最终 OOM。解法:限制 range 读取长度 + 定期 trim + 修正永不过期的 key。

线程池 7 参数与工作原理

7 个参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(空闲存活时间)、unit(时间单位)、workQueue(阻塞队列)、threadFactory(线程工厂)、rejectedExecutionHandler(拒绝策略)。

执行流程:核心线程未满 → 创建核心线程执行;核心线程满 → 任务入队;队列满且最大线程未满 → 创建临时线程;全部满 → 触发拒绝策略。

拒绝策略:AbortPolicy(抛异常,默认)、CallerRunsPolicy(调用者线程执行)、DiscardPolicy(静默丢弃)、DiscardOldestPolicy(丢弃队首旧任务再重试)。

Tomcat 线程池 与 JDK 不同:先扩到 max 再入队,优先扩线程应对突发,而非 JDK 的「先核心 → 再入队 → 再扩」。

最佳实践:不要用 Executors 快捷方法(newFixedThreadPool 无界队列 OOM、newCachedThreadPool 无限线程)。自定义线程池,根据 IO 密集型(2N+1)还是 CPU 密集型(N+1)设置核心线程数。

MySQL 索引:B+Tree 为什么赢

B+Tree vs 其他:Hash 索引不支持范围查询、排序、最左匹配;二叉树退化为链表;AVL/红黑树高度大(磁盘 IO 次数多);B-Tree 非叶子节点存储数据 → 单页存储索引项少 → 树更高。

B+Tree 优势:① 所有数据存叶子节点,非叶子只存索引 → 单页索引项多 → 树更矮 → IO 更少;② 叶子节点形成有序双向链表 → 范围查询顺链扫即可;③ 查询任何数据路径等长,稳定。

聚簇 vs 非聚簇:InnoDB 主键索引叶子存完整行数据(聚簇),二级索引叶子存主键值 → 回表。MyISAM 索引叶子存数据地址,数据文件与索引文件分离。

最左前缀原则:联合索引 (a, b, c) 相当于建了 a、a,b、a,b,c 三个索引。跳过 a 直接用 b 不走索引。a like '%xxx' 不走,a like 'xxx%' 可以。

实战 多业务共用底表累积 3000 万行,部分业务依赖底表筛选效率低下。一次 ALTER TABLE 锁表 40 分钟触发全业务阻塞,推动按业务拆分,核心查询从 40 秒+ 优化至 2~3 秒。