首页
壁纸
统计
友链
Search
1
Vue2详细笔记
37 阅读
2
Nestjs概述-中文
19 阅读
3
ExpressAPI
17 阅读
4
答辩
14 阅读
5
JavaScript企业数据处理实用指南
13 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
36
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
36
篇与
的结果
2026-04-09
并发编程基础概念
一、并发编程基础概念1.1 并发与并行并发:多任务交替执行(单核/多核),依赖 CPU 时间片轮转。宏观上是同时进行的,微观上是串行。并行:多任务同时执行(必须多核),利用多个 CPU 核心同时处理不同任务。1.2 Java 内存模型 (JMM)主内存:所有线程共享,存储实例变量、静态变量。工作内存:每个线程私有,存储主内存变量的副本。线程不能直接操作主内存,必须先读入工作内存,操作完成后再写回。JMM 定义的 8 种内存交互操作(原子指令):lock(锁定):作用于主内存,将变量标识为线程独占。unlock(解锁):作用于主内存,释放独占状态。read(读取):作用于主内存,将变量值传输到工作内存。load(载入):作用于工作内存,将 read 的值放入变量副本。use(使用):作用于工作内存,将变量值传递给执行引擎。assign(赋值):作用于工作内存,将执行引擎的值赋给变量副本。store(存储):作用于工作内存,将变量值传送到主内存。write(写入):作用于主内存,将 store 的值写入主内存变量。1.3 线程安全问题的根源与表现根源:主内存与工作内存数据不一致。竞态条件:结果依赖线程执行的相对时序(如 check-then-act)。数据竞争:多线程访问同一内存,至少一个写操作,且无同步。死锁:两个及以上线程互相持有对方需要的锁,永久阻塞。二、并发编程三大特性(详细版)特性详细描述实现手段可见性线程 A 修改变量后,线程 B 能立即看到变化。volatile、synchronized、Lock(锁释放前刷新主存)、final原子性一个或多个操作要么全执行,要么全不执行,中间不被调度中断。synchronized、Lock、java.util.concurrent.atomic 原子类有序性避免指令重排序带来的逻辑错误。单线程内语义表现为串行。volatile(内存屏障)、synchronized(锁释放与获取原则)三、线程基础 API 详解3.1 创建线程的方式(含代码细节)方式一:继承 Thread 类public class ThreadExample extends Thread { @Override public void run() { System.out.println("Thread is running"); } public static void main(String[] args) { ThreadExample t = new ThreadExample(); t.start(); } }方式二:实现 Runnable 接口public class RunnableExample implements Runnable { @Override public void run() { System.out.println("Runnable task is running"); } public static void main(String[] args) { Thread t = new Thread(new RunnableExample()); t.start(); // Lambda 简化写法 new Thread(() -> System.out.println("Lambda style")).start(); } }方式三:实现 Callable 接口(有返回值、可抛异常)import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CallableExample implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); return "Callable result"; } public static void main(String[] args) throws Exception { FutureTask<String> futureTask = new FutureTask<>(new CallableExample()); new Thread(futureTask).start(); // 阻塞获取结果 System.out.println(futureTask.get()); } }3.2 Thread 类构造方法与核心控制构造方法细节:Thread():默认线程名 "Thread-" + n。Thread(String name):指定线程名。Thread(Runnable target):指定任务。Thread(Runnable target, String name):指定任务与名称。线程优先级与守护线程:Thread t = new Thread(); t.setPriority(Thread.MAX_PRIORITY); // 10(最高) t.setPriority(Thread.NORM_PRIORITY); // 5(默认) t.setPriority(Thread.MIN_PRIORITY); // 1(最低) // 守护线程:JVM 退出时不管守护线程是否结束 Thread daemonT = new Thread(() -> { while (true) { /* 后台任务,如日志清理 */ } }); daemonT.setDaemon(true); // 必须在 start() 前设置 daemonT.start();3.3 线程中断机制(方法签名详解)void interrupt():设置中断标志位为 true。boolean isInterrupted():检查标志位,不清除。static boolean interrupted():检查当前线程标志位,并清除(重置为 false)。Thread t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // 业务逻辑 try { Thread.sleep(1000); } catch (InterruptedException e) { // 捕获异常后标志位会被清除,需手动再次中断以退出循环 Thread.currentThread().interrupt(); } } });四、synchronized 锁机制深度解析4.1 锁的三种形态(代码示例)对象锁(锁当前实例 this):class SyncDemo { // 同步实例方法 public synchronized void method1() { /* 锁 this */ } // 同步代码块 public void method2() { synchronized (this) { /* 锁 this */ } } }类锁(锁 Class 对象):class SyncDemo { // 同步静态方法 public static synchronized void staticMethod1() { /* 锁 SyncDemo.class */ } public void staticMethod2() { synchronized (SyncDemo.class) { /* 锁类对象 */ } } }4.2 锁升级机制(Java 6+,不可逆)偏向锁:无竞争时,在对象头记录线程 ID,线程再次进入仅需检查 ID,无需 CAS。轻量级锁:交替执行时,通过 CAS 自旋尝试获取锁,不阻塞线程(消耗 CPU)。重量级锁:激烈竞争时,自旋失败升级为 OS 互斥量,线程挂起进入阻塞队列(开销大)。五、volatile 与内存屏障5.1 volatile 特性总结保证可见性:写 volatile 变量时强制刷新工作内存到主内存;读 volatile 变量时强制从主内存加载。禁止重排序:通过插入内存屏障防止 CPU/编译器对指令进行重排。不保证原子性:i++ 操作(读-改-写)依然不安全。5.2 内存屏障分类(JMM 规范)屏障类型作用示例场景LoadLoad屏障前读操作完成,才能执行屏障后读操作。读变量A; LoadLoad; 读变量B;LoadStore屏障前读操作完成,才能执行屏障后写操作。读变量A; LoadStore; 写变量B;StoreStore屏障前写操作完成并可见,才能执行屏障后写操作。写变量A; StoreStore; 写变量B;StoreLoad屏障前写操作完成并可见,才能执行屏障后读操作。开销最大,兼具其他三者功能。写变量A; StoreLoad; 读变量B;volatile 屏障插入策略:volatile 写:前插 StoreStore,后插 StoreLoad。volatile 读:后插 LoadLoad + LoadStore。六、Lock 接口与 ReentrantLock 详细 API6.1 Lock 接口方法清单方法详细描述void lock()阻塞获取锁,不可中断,死等。void lockInterruptibly()可响应中断的获取锁,等待时若线程被中断则抛 InterruptedException。boolean tryLock()非阻塞,获取到返回 true,否则立即返回 false。boolean tryLock(long time, TimeUnit unit)超时等待,超时返回 false;等待时可响应中断。void unlock()释放锁。必须在 finally 块中调用。Condition newCondition()返回绑定此锁的条件变量。6.2 ReentrantLock 使用模板class X { private final ReentrantLock lock = new ReentrantLock(); public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock(); // 关键:防止死锁 } } }6.3 公平锁与非公平锁构造// 非公平锁(默认,吞吐量高) ReentrantLock unfairLock = new ReentrantLock(); ReentrantLock unfairLock2 = new ReentrantLock(false); // 公平锁(FIFO,避免线程饥饿但上下文切换多) ReentrantLock fairLock = new ReentrantLock(true);6.4 可中断与超时示例// 可中断锁示例 try { lock.lockInterruptibly(); // 临界区 } catch (InterruptedException e) { // 线程被中断,退出等待队列 } finally { lock.unlock(); } // 超时锁示例 if (lock.tryLock(2, TimeUnit.SECONDS)) { try { // 获取成功 } finally { lock.unlock(); } } else { // 执行备选方案 }七、读写锁与 StampedLock7.1 ReentrantReadWriteLock 特性读写互斥:读锁与写锁互斥。锁降级:持有写锁的线程可以获取读锁,然后释放写锁,降级为读锁。不支持锁升级:持有读锁时直接获取写锁会导致死锁,JVM 禁止此行为。ReadWriteLock rwLock = new ReentrantReadWriteLock(); Lock readLock = rwLock.readLock(); Lock writeLock = rwLock.writeLock(); // 缓存更新场景:锁降级示例 writeLock.lock(); try { // 1. 更新数据库/缓存 readLock.lock(); // 在释放写锁前获取读锁 } finally { writeLock.unlock(); // 降级为读锁 } try { // 2. 后续读取操作(保证数据可见性) } finally { readLock.unlock(); }7.2 StampedLock 三种模式(Java 8+,不可重入)写锁:long stamp = stampedLock.writeLock(); 返回邮戳。悲观读锁:long stamp = stampedLock.readLock();乐观读:无锁机制,依赖版本校验。class Point { private double x, y; private final StampedLock sl = new StampedLock(); // 乐观读(读多写少性能最优) double distanceFromOrigin() { long stamp = sl.tryOptimisticRead(); // 获取乐观读邮戳 double currentX = x, currentY = y; // 无锁读取 if (!sl.validate(stamp)) { // 校验邮戳是否失效 stamp = sl.readLock(); // 失效则升级为悲观读 try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }八、Condition 条件变量(替换 Object Monitor)8.1 核心方法对比Condition 方法Object 对应方法说明await()wait()释放锁并等待。必须在 while 循环中调用以防虚假唤醒。signal()notify()唤醒一个等待线程。signalAll()notifyAll()唤醒所有等待线程。awaitUninterruptibly()无不可中断等待。awaitUntil(Date deadline)wait(long timeout)带截止时间等待。8.2 生产者-消费者模型(多条件队列)class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); // 防御性编程:必须用 while 循环 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) // 防止虚假唤醒 notFull.await(); // ... 入队逻辑 notEmpty.signal(); } finally { lock.unlock(); } } }九、原子类与 CAS 底层原理9.1 AtomicInteger 常用方法方法签名操作描述int get()获取当前值。void set(int newValue)设置新值(无原子性保证,仅可见性)。int getAndSet(int newValue)设置新值,返回旧值。boolean compareAndSet(expect, update)CAS 核心操作。int getAndIncrement()等价于 i++。int incrementAndGet()等价于 ++i。int getAndAdd(int delta)加 delta 并返回旧值。9.2 ABA 问题与解决方案ABA 问题描述:线程 1 读取值 A,线程 2 改为 B,线程 3 又改回 A。线程 1 使用 CAS 检查时发现值仍为 A,误以为未变化而更新成功。解决方案:AtomicStampedReference 引入版本号。// 泛型类:AtomicStampedReference<V> AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0); // 初始值100,版本号0 int stamp = ref.getStamp(); int oldValue = ref.getReference(); // 更新时需同时匹配值和时间戳 boolean success = ref.compareAndSet(oldValue, 200, stamp, stamp + 1);
2026年04月09日
8 阅读
0 评论
0 点赞
2026-04-09
Java 多线程与线程池完整笔记
Java 多线程与线程池完整笔记一、线程池基础1.1 什么是线程池线程池是一种多线程处理模式,它在系统启动时预先创建一定数量的线程放入池中。这些线程在没有任务时处于等待状态,当有任务提交时,池中某个空闲线程会被唤醒执行任务。任务执行完成后,线程并不会被销毁,而是返回池中继续等待下一个任务。┌─────────────────────────────────────────────────┐ │ 线程池架构 │ ├─────────────────────────────────────────────────┤ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │线程1│ │线程2│ │线程3│ │线程N│ ← 常驻线程 │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ │ │ │ └─────────┴────┬────┴─────────┘ │ │ ▼ │ │ ┌───────────────┐ │ │ │ 任务队列 │ ← 缓冲待处理任务 │ │ └───────────────┘ │ └─────────────────────────────────────────────────┘1.2 为什么需要线程池优势说明资源复用避免频繁创建和销毁线程的开销,线程对象可重复使用响应速度任务到达时无需等待线程创建,立即执行可管理性统一分配、调优和监控线程,防止资源耗尽流量控制通过队列和最大线程数限制并发量,保护系统二、ThreadPoolExecutor 核心参数详解2.1 七个核心参数public ThreadPoolExecutor( int corePoolSize, // 1. 核心线程数 int maximumPoolSize, // 2. 最大线程数 long keepAliveTime, // 3. 空闲线程存活时间 TimeUnit unit, // 4. 时间单位 BlockingQueue<Runnable> workQueue, // 5. 工作队列 ThreadFactory threadFactory, // 6. 线程工厂 RejectedExecutionHandler handler // 7. 拒绝策略 )参数含义比喻corePoolSize核心线程数,即使空闲也保留在池中的线程数量餐厅正式员工,永不辞退maximumPoolSize最大线程数 = 核心线程 + 临时线程正式员工 + 临时工的上限keepAliveTime临时线程空闲超过该时间则被回收临时工多久没活干就辞退unitkeepAliveTime 的时间单位秒、毫秒等workQueue存放待执行任务的阻塞队列餐厅等位区threadFactory创建线程的工厂,可自定义线程名、优先级等员工招聘标准handler队列满且线程达最大值时的拒绝策略客满时的处理方式2.2 常用工作队列类型队列类型特点适用场景SynchronousQueue不存储任务,直接交给线程执行CachedThreadPoolLinkedBlockingQueue无界队列(默认 Integer.MAX_VALUE)FixedThreadPoolArrayBlockingQueue有界队列,需指定容量需限制队列长度的场景PriorityBlockingQueue优先级队列任务有优先级要求三、线程池执行流程(五步核心逻辑)// 伪代码表示执行逻辑 public void execute(Runnable task) { // 步骤1:当前线程数 < corePoolSize if (workerCount < corePoolSize) { addWorker(task, true); // 新建核心线程执行 return; } // 步骤2:核心线程已满,尝试放入队列 if (workQueue.offer(task)) { // 放入成功,等待空闲线程取走执行 return; } // 步骤3:队列已满,尝试创建临时线程 if (workerCount < maximumPoolSize) { addWorker(task, false); // 新建临时线程执行 return; } // 步骤4:线程数已达最大值,执行拒绝策略 reject(task); }流程图:提交任务 │ ▼ 当前线程数 < corePoolSize? ──Yes──▶ 创建核心线程执行 │ No │ ▼ 任务能否入队? ──Yes──▶ 入队等待执行 │ No(队列已满) │ ▼ 当前线程数 < maximumPoolSize? ──Yes──▶ 创建临时线程执行 │ No │ ▼ 执行拒绝策略四、四种拒绝策略策略类行为比喻使用建议AbortPolicy抛出 RejectedExecutionException直接赶走客人并吵架默认策略,开发常用,快速失败CallerRunsPolicy由提交任务的线程自己执行老板亲自服务客人可减缓提交速度,重要任务兜底DiscardPolicy静默丢弃新任务悄悄赶走客人不重要的任务,如日志上报DiscardOldestPolicy丢弃队列中最老的任务赶走排队最久的人优先处理新任务// 自定义拒绝策略示例 new ThreadPoolExecutor.AbortPolicy() // 抛异常 new ThreadPoolExecutor.CallerRunsPolicy() // 调用者执行 new ThreadPoolExecutor.DiscardPolicy() // 直接丢弃 new ThreadPoolExecutor.DiscardOldestPolicy() // 丢弃最旧的五、Executors 四种快捷线程池⚠️ 开发规范:强制禁止使用 Executors 创建线程池,必须通过 ThreadPoolExecutor 构造函数显式指定参数。原因:FixedThreadPool 和 SingleThreadPool 使用无界队列,可能 OOMCachedThreadPool 和 ScheduledThreadPool 允许创建无限数量线程,可能 OOM工厂方法核心特点适用场景风险newFixedThreadPool(n)固定线程数,无界队列重负载服务器队列无限堆积newCachedThreadPool()0核心,无限临时线程,同步队列大量短期异步任务线程数爆炸newSingleThreadExecutor()单线程,无界队列任务需顺序执行队列无限堆积newScheduledThreadPool(n)支持定时/周期任务定时调度线程数爆炸// ❌ 错误示范 ExecutorService pool = Executors.newFixedThreadPool(10); // ✅ 正确示范 ExecutorService pool = new ThreadPoolExecutor( 10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new ThreadFactoryBuilder().setNameFormat("my-pool-%d").build(), new ThreadPoolExecutor.CallerRunsPolicy() );六、线程数设置公式6.1 CPU 密集型任务定义:大量时间用于计算,很少等待 I/O线程数 = CPU 核心数 + 1加 1 是为了在某个线程因缺页中断等原因暂停时,有备用线程顶上,最大化 CPU 利用率。6.2 I/O 密集型任务定义:大量时间等待 I/O 操作(网络、磁盘、数据库)线程数 = CPU 核心数 × 2更精确的公式:线程数 = CPU 核心数 × (1 + 平均等待时间 / 平均计算时间)6.3 混合型任务将任务拆分为 CPU 密集和 I/O 密集两部分,分别用不同线程池处理。七、线程池生命周期与优雅关闭7.1 五种状态状态说明接收新任务执行队列任务RUNNING正常运行✅✅SHUTDOWN关闭中(调用 shutdown())❌✅STOP强制停止(调用 shutdownNow())❌❌TIDYING过渡状态,所有任务终止❌❌TERMINATED完全终止❌❌7.2 优雅关闭代码模板public void shutdownGracefully(ExecutorService pool) { pool.shutdown(); // ① 停止接收新任务 try { // ② 等待现有任务完成,最多等待 60 秒 if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // ③ 超时则强制关闭 // ④ 再等待 5 秒,确认是否终止 if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { System.err.println("线程池未能正常终止"); } } } catch (InterruptedException e) { pool.shutdownNow(); // ⑤ 当前线程被中断,也强制关闭 Thread.currentThread().interrupt(); } }八、ScheduledThreadPoolExecutor 定时任务线程池8.1 概述ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor,专门用于执行定时和周期性任务,是传统 Timer 的替代品。对比项TimerScheduledThreadPoolExecutor线程模型单线程多线程池异常处理任务抛异常则 Timer 线程终止单任务异常不影响其他任务任务调度基于绝对时间基于相对时间,更精准适用场景简单定时生产级定时调度8.2 三种核心 APIScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); // 1. 延迟执行一次 ScheduledFuture<?> future = scheduler.schedule( () -> System.out.println("延迟执行"), 5, TimeUnit.SECONDS ); // 2. 固定频率执行(不管上次是否完成) scheduler.scheduleAtFixedRate( () -> System.out.println("固定频率"), 1, // 首次延迟 1 秒 3, // 每 3 秒执行一次 TimeUnit.SECONDS ); // 3. 固定延迟执行(等上次完成后再延迟) scheduler.scheduleWithFixedDelay( () -> System.out.println("固定延迟"), 1, // 首次延迟 1 秒 3, // 上次完成后等待 3 秒 TimeUnit.SECONDS );8.3 scheduleAtFixedRate vs scheduleWithFixedDelay方法调度方式效果scheduleAtFixedRate固定频率(周期固定)不管任务执行多久,严格按周期触发,可能任务堆叠scheduleWithFixedDelay固定延迟(间隔固定)等上次执行完,再等固定时间才触发,周期 = 执行时间 + 延迟scheduleAtFixedRate (周期 = 3秒) 任务执行2秒 → 等待1秒 → 下一轮 任务执行4秒 → 立即触发下一轮(无等待) scheduleWithFixedDelay (延迟 = 3秒) 任务执行2秒 → 等待3秒 → 下一轮(间隔5秒) 任务执行4秒 → 等待3秒 → 下一轮(间隔7秒)九、线程池性能监控与调优9.1 关键监控指标指标含义告警阈值排查方向活跃线程数当前正在工作的线程数> 最大线程数 80%线程数不足或任务耗时过长队列长度等待执行的任务数> 队列容量 70%消费速度跟不上生产速度任务完成数已完成任务累计增长停滞线程池可能卡死或阻塞拒绝任务数被拒绝的任务累计> 0(正常应为 0)线程数和队列容量需调大线程池大小当前池中线程总数长期接近 maximumPoolSize调大 maximumPoolSize任务等待时间任务从提交到执行的时间> 业务可接受阈值处理能力不足9.2 监控实现方案@Component public class ThreadPoolMonitor { private final ThreadPoolExecutor executor; @Scheduled(fixedDelay = 30000) // 每30秒采集一次 public void monitor() { int activeCount = executor.getActiveCount(); long completedCount = executor.getCompletedTaskCount(); int queueSize = executor.getQueue().size(); int poolSize = executor.getPoolSize(); long rejectCount = // 自定义记录 log.info("线程池监控 - 活跃:{}, 队列:{}, 池大小:{}, 已完成:{}, 拒绝:{}", activeCount, queueSize, poolSize, completedCount, rejectCount); // 告警判断 if (queueSize > queueCapacity * 0.7) { alert("队列使用率超过70%"); } } }9.3 调优策略现象可能原因解决方案队列持续增长消费速度 < 生产速度增加线程数,或优化任务执行效率活跃线程数持续满任务耗时过长拆分任务、异步化、增加最大线程数拒绝次数频繁线程池容量不足增大队列容量和最大线程数CPU 使用率低但队列满线程阻塞于 I/O增加线程数(I/O 密集型公式)OOM 内存溢出无界队列堆积改用有界队列 + 合理拒绝策略十、最佳实践总结10.1 编码规范// ✅ 正确:显式创建 ThreadPoolExecutor ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, // 核心线程数 20, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲超时 new ArrayBlockingQueue<>(1000), // 有界队列 new NamedThreadFactory("biz-pool"), // 自定义线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); // ❌ 错误:使用 Executors 快捷方法 ExecutorService pool = Executors.newFixedThreadPool(10);10.2 必须设置的内容[ ] 有界队列(避免 OOM)[ ] 合适的拒绝策略(根据业务选型)[ ] 有意义的线程名称(便于排查问题)[ ] 监控指标采集(JMX / Micrometer)[ ] 优雅关闭逻辑(应用停止时调用)10.3 参数选择参考业务类型corePoolSizemaximumPoolSize队列类型队列容量计算密集型 APICPU 核数 + 1CPU 核数 + 1ArrayBlockingQueue100~500I/O 密集型 APICPU 核数 × 2CPU 核数 × 4ArrayBlockingQueue500~2000后台批量处理1~51~10LinkedBlockingQueue10000+即时响应任务10~5050~200SynchronousQueue0本笔记涵盖了 Java 多线程线程池的核心知识点,建议结合实战加深理解。
2026年04月09日
4 阅读
0 评论
1 点赞
2026-04-09
Java多线程与线程池
# Java多线程与线程池目录线程池基础再深入ThreadPoolExecutor 源码级剖析阻塞队列深度对比与选型拒绝策略详解与自定义实战线程工厂定制与线程命名规范线程池动态调整与运行时管理任务提交与结果处理进阶线程池钩子方法与扩展点ScheduledThreadPoolExecutor 深度解析线程池监控与告警体系搭建常见陷阱与最佳实践生产级线程池配置模板一、线程池基础再深入1.1 线程池的设计思想 —— 池化技术池化技术的核心是空间换时间,预先创建资源对象,使用时直接获取,用完后归还,避免频繁创建销毁的开销。除了线程池,常见的池化技术还有:数据库连接池(HikariCP、Druid)对象池(Apache Commons Pool)内存池(Netty 的 ByteBuf)1.2 线程池解决了什么问题?(细化版)问题线程池解决方案线程创建开销大线程对象复用,避免反复 new Thread().start() 的系统调用线程数量失控通过 maximumPoolSize 和队列容量双重限制并发上限任务堆积导致 OOM使用有界队列 + 拒绝策略,防止无限制接受任务线程生命周期管理复杂统一提供 shutdown() / shutdownNow() 接口任务执行情况难以追踪提供 beforeExecute / afterExecute 钩子和统计指标1.3 线程池的替代方案比较方案优点缺点new Thread(runnable).start()简单直接无复用,资源浪费,难以管理Timer支持定时单线程,异常即停,不推荐ThreadPoolExecutor生产级标准参数较多,需理解原理ForkJoinPool工作窃取,适合递归分治任务不适合普通任务CompletableFuture + 自定义线程池异步编程更优雅学习曲线稍高二、ThreadPoolExecutor 源码级剖析2.1 核心数据结构 —— ctl 原子变量ThreadPoolExecutor 使用一个 AtomicInteger 类型的 ctl 变量,同时存储两个信息:高 3 位:线程池运行状态(RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED)低 29 位:当前工作线程数量(workerCount)private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; // 29 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 约 5.3 亿 // 状态常量(高3位) private static final int RUNNING = -1 << COUNT_BITS; // 111... private static final int SHUTDOWN = 0 << COUNT_BITS; // 000... private static final int STOP = 1 << COUNT_BITS; // 001... private static final int TIDYING = 2 << COUNT_BITS; // 010... private static final int TERMINATED = 3 << COUNT_BITS; // 011... // 拆包方法 private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; }状态转换图: RUNNING │ shutdown() ▼ SHUTDOWN ──队列为空──▶ TIDYING ── terminated()──▶ TERMINATED │ │ shutdownNow() ▼ STOP ──────────────▶ TIDYING ── terminated()──▶ TERMINATED2.2 内部类 Worker —— 工作线程的封装private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; // 实际执行任务的线程 Runnable firstTask; // 构造时可能携带的首个任务 volatile long completedTasks; // 完成任务计数 Worker(Runnable firstTask) { setState(-1); // 初始禁止中断,直到线程启动 this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } public void run() { runWorker(this); // 核心循环 } }Worker 继承 AQS 实现了简单的非重入互斥锁,目的是:在执行任务期间,防止其他线程中断正在运行任务的 Worker(interruptIdleWorkers 只中断空闲的)锁状态表示 Worker 是否正在执行任务(state >= 1 表示忙碌)2.3 runWorker —— 线程池运行核心循环final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // 允许中断 boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // 1. 检查线程池状态,必要时中断自己 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); // 钩子:任务执行前 Throwable thrown = null; try { task.run(); // 执行任务 } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); // 钩子:任务执行后 } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }2.4 getTask —— 从队列获取任务(包含线程销毁逻辑)private Runnable getTask() { boolean timedOut = false; for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 检查是否需要减少 Worker if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // 是否允许核心线程超时 或 当前线程数超过核心数 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 根据 timed 决定是 poll(带超时)还是 take(阻塞) Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }关键点:非核心线程通过 poll 超时返回 null 后会被回收核心线程默认 take 无限阻塞,除非设置 allowCoreThreadTimeOut(true)三、阻塞队列深度对比与选型3.1 常用阻塞队列底层结构与特性队列底层结构锁机制容量适用场景ArrayBlockingQueue数组单锁(ReentrantLock)必须指定固定容量、内存占用可控LinkedBlockingQueue单向链表两把锁(putLock/takeLock)默认 Integer.MAX_VALUE吞吐量高、无需预知容量SynchronousQueue无存储公平模式队列/非公平栈0直接交付,CachedThreadPoolPriorityBlockingQueue二叉堆数组单锁无界任务有优先级排序DelayQueue优先队列单锁无界定时任务存储3.2 ArrayBlockingQueue vs LinkedBlockingQueue 性能差异// 压测对比结论(仅供参考) // ArrayBlockingQueue:入队出队共用一把锁,并发竞争激烈时性能下降 // LinkedBlockingQueue:入队和出队各用一把锁,吞吐量更高,但内存占用更大 // 选择建议: // - 内存敏感、容量可预估 → ArrayBlockingQueue // - 追求高吞吐、内存较充裕 → LinkedBlockingQueue(需指定容量)3.3 SynchronousQueue 的两种模式// 公平模式:TransferQueue(FIFO) new SynchronousQueue<>(true); // 非公平模式:TransferStack(LIFO)——默认 new SynchronousQueue<>();SynchronousQueue 内部没有容量,每个插入操作必须等待另一个线程的移除操作。CachedThreadPool 使用它,使得任务立即交给线程执行,不积压。四、拒绝策略详解与自定义实战4.1 JDK 内置四种策略源码分析// AbortPolicy public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } // CallerRunsPolicy public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); // 直接在调用者线程执行 } } // DiscardPolicy public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // do nothing } // DiscardOldestPolicy public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); // 丢弃队头 e.execute(r); // 重新尝试提交 } }4.2 自定义拒绝策略实战(带日志与监控)public class MonitoredCallerRunsPolicy implements RejectedExecutionHandler { private static final Logger log = LoggerFactory.getLogger(MonitoredCallerRunsPolicy.class); private final MeterRegistry meterRegistry; private final Counter rejectCounter; public MonitoredCallerRunsPolicy(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.rejectCounter = Counter.builder("thread.pool.reject") .description("线程池拒绝任务计数") .register(meterRegistry); } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { rejectCounter.increment(); log.warn("线程池 {} 任务被拒绝,当前线程数: {}/{}, 队列大小: {}/{},降级为调用者执行", e.toString(), e.getPoolSize(), e.getMaximumPoolSize(), e.getQueue().size(), e.getQueue().remainingCapacity() + e.getQueue().size()); if (!e.isShutdown()) { // 在调用者线程中执行,并记录耗时(可选) long start = System.nanoTime(); try { r.run(); } finally { log.debug("降级任务执行耗时: {}ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)); } } } }4.3 其他常见自定义策略// 1. 阻塞等待策略(当队列满时,让提交线程阻塞一小段时间再尝试) public class BlockingWaitPolicy implements RejectedExecutionHandler { private final long maxWaitMs; public BlockingWaitPolicy(long maxWaitMs) { this.maxWaitMs = maxWaitMs; } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { try { if (!e.getQueue().offer(r, maxWaitMs, TimeUnit.MILLISECONDS)) { throw new RejectedExecutionException("等待超时后仍无法提交"); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new RejectedExecutionException("等待期间被中断", ex); } } } } // 2. 持久化到数据库/消息队列的策略 public class PersistencePolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { // 将任务序列化存储到 DB 或 MQ,后续补偿 TaskStorage.save((Serializable) r); log.info("任务被拒绝,已持久化待补偿"); } }五、线程工厂定制与线程命名规范5.1 自定义 ThreadFactory 模板public class NamedThreadFactory implements ThreadFactory { private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; private final boolean daemon; private final int priority; private final Thread.UncaughtExceptionHandler exceptionHandler; public NamedThreadFactory(String poolName) { this(poolName, false, Thread.NORM_PRIORITY); } public NamedThreadFactory(String poolName, boolean daemon, int priority) { this.namePrefix = poolName + "-thread-"; this.daemon = daemon; this.priority = priority; this.exceptionHandler = (t, e) -> log.error("线程 {} 发生未捕获异常", t.getName(), e); } @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement()); t.setDaemon(daemon); t.setPriority(priority); t.setUncaughtExceptionHandler(exceptionHandler); return t; } }5.2 使用示例ThreadPoolExecutor executor = new ThreadPoolExecutor( 10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new NamedThreadFactory("order-processor", false, Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy() );5.3 守护线程与用户线程的区别类型行为适用场景用户线程(默认)JVM 等待所有用户线程结束后才退出执行业务逻辑的线程池守护线程JVM 退出时立即终止,不管是否执行完后台辅助任务(如日志刷盘)六、线程池动态调整与运行时管理6.1 可动态调整的参数// 以下方法均可在运行时调用 executor.setCorePoolSize(newCoreSize); executor.setMaximumPoolSize(newMaxSize); executor.setKeepAliveTime(newTime, TimeUnit.SECONDS); executor.setRejectedExecutionHandler(newHandler); executor.setThreadFactory(newFactory); // 仅影响后续新建线程 executor.allowCoreThreadTimeOut(true); // 允许核心线程超时回收6.2 动态调整实战:基于负载的自动扩缩容@Component public class AdaptiveThreadPoolManager { private final ThreadPoolExecutor executor; private final int minCoreSize; private final int maxCoreSize; @Scheduled(fixedDelay = 10000) public void adjustPoolSize() { int queueSize = executor.getQueue().size(); int activeCount = executor.getActiveCount(); int currentCore = executor.getCorePoolSize(); // 队列堆积严重且活跃线程接近核心数 → 增加核心线程 if (queueSize > 500 && activeCount >= currentCore * 0.9) { int newCore = Math.min(currentCore + 2, maxCoreSize); executor.setCorePoolSize(newCore); log.info("队列堆积,增加核心线程至 {}", newCore); } // 队列持续为空且核心线程数大于最小值 → 减少核心线程 if (queueSize == 0 && activeCount < currentCore * 0.3 && currentCore > minCoreSize) { int newCore = Math.max(currentCore - 1, minCoreSize); executor.setCorePoolSize(newCore); log.info("队列空闲,减少核心线程至 {}", newCore); } } }6.3 预热核心线程// 提前创建所有核心线程,避免懒加载导致初期响应慢 executor.prestartAllCoreThreads(); // 或者预热指定数量 executor.prestartCoreThread();七、任务提交与结果处理进阶7.1 execute vs submit 的区别方法返回值异常处理execute(Runnable)void异常抛出到 UncaughtExceptionHandlersubmit(Runnable)Future<?>异常封装在 Future.get() 中submit(Callable<T>)Future<T>异常封装在 Future.get() 中// execute 的异常不会被吞掉,会传播到线程的 UncaughtExceptionHandler executor.execute(() -> { throw new RuntimeException("execute异常"); }); // submit 的异常会被封装在 Future 中,必须调用 get() 才会抛出 Future<?> future = executor.submit(() -> { throw new RuntimeException("submit异常"); }); try { future.get(); // 这里会抛出 ExecutionException } catch (ExecutionException e) { e.getCause(); // 原始异常 }7.2 CompletionService —— 先完成先获取当需要处理一批任务并按照完成顺序获取结果时,使用 CompletionService 比手动遍历 Future 列表更高效。ExecutorService executor = Executors.newFixedThreadPool(10); CompletionService<String> completionService = new ExecutorCompletionService<>(executor); // 提交多个任务 List<Future<String>> futures = new ArrayList<>(); for (int i = 0; i < 100; i++) { final int taskId = i; futures.add(completionService.submit(() -> { Thread.sleep(ThreadLocalRandom.current().nextInt(1000)); return "Task-" + taskId + " result"; })); } // 按完成顺序获取结果 for (int i = 0; i < 100; i++) { Future<String> completed = completionService.take(); // 阻塞等待下一个完成的任务 String result = completed.get(); System.out.println(result); }7.3 invokeAll 与 invokeAnyList<Callable<String>> tasks = Arrays.asList( () -> { Thread.sleep(1000); return "A"; }, () -> { Thread.sleep(500); return "B"; } ); // invokeAll:等待所有任务完成,返回所有 Future List<Future<String>> allFutures = executor.invokeAll(tasks); // invokeAny:只要有一个任务成功完成就返回其结果,其他任务取消 String firstResult = executor.invokeAny(tasks); // 返回 "B"八、线程池钩子方法与扩展点8.1 三个可重写的钩子方法public class MonitoredThreadPool extends ThreadPoolExecutor { public MonitoredThreadPool(...) { super(...); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); // 可以在此记录任务开始时间,存入 ThreadLocal TaskContext.start(t.getName(), r); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); try { if (t != null) { log.error("任务执行异常", t); metrics.recordError(); } // 记录任务执行耗时 long duration = TaskContext.getDuration(); metrics.recordTaskDuration(duration); } finally { TaskContext.clear(); } } @Override protected void terminated() { super.terminated(); log.info("线程池已完全终止"); metrics.unregister(); } }8.2 使用 ThreadLocal 传递上下文(需注意清理)public class TaskContext { private static final ThreadLocal<Long> START_TIME = new ThreadLocal<>(); private static final ThreadLocal<String> TASK_NAME = new ThreadLocal<>(); public static void start(String threadName, Runnable task) { START_TIME.set(System.nanoTime()); TASK_NAME.set(task.getClass().getSimpleName()); } public static long getDuration() { Long start = START_TIME.get(); return start == null ? -1 : TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); } public static void clear() { START_TIME.remove(); // ⚠️ 必须清理,否则会导致内存泄漏 TASK_NAME.remove(); } }为什么必须清理 ThreadLocal?线程池中的线程是复用的,如果不在 afterExecute 中清理,ThreadLocal 中的数据会残留到下一个任务中,导致数据错乱或内存泄漏(Value 对象一直被强引用)。九、ScheduledThreadPoolExecutor 深度解析9.1 架构与继承关系ThreadPoolExecutor ▲ │ ScheduledThreadPoolExecutor │ └── 内部类 ScheduledFutureTask (实现 RunnableScheduledFuture) └── 内部类 DelayedWorkQueue (基于堆的延迟队列)9.2 DelayedWorkQueue 实现原理数据结构:二叉小顶堆,按照任务的下次执行时间排序线程等待机制:Leader-Follower 模式,只有一个 Leader 线程等待堆顶任务的到期时间,其他线程无限期等待,减少不必要的唤醒// 简化的 Leader-Follower 逻辑 public RunnableScheduledFuture<?> take() throws InterruptedException { lock.lockInterruptibly(); try { for (;;) { RunnableScheduledFuture<?> first = queue[0]; if (first == null) { available.await(); // 无任务,等待 } else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0) { return finishPoll(first); // 任务到期,取出 } first = null; if (leader != null) { available.await(); // 已有 Leader,当前线程等待 } else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); // 作为 Leader 等待 } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && queue[0] != null) available.signal(); // 唤醒一个 Follower lock.unlock(); } }9.3 ScheduledFutureTask 关键字段private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> { private long time; // 下次执行时间(纳秒) private final long period; // 周期:正数 fixedRate,负数 fixedDelay,0 表示单次 private final long sequenceNumber; // 全局序号,用于任务排序 }9.4 scheduleAtFixedRate 与 scheduleWithFixedDelay 源码差异// 在 run 方法中重新设置下次执行时间 public void run() { boolean periodic = isPeriodic(); if (!canRunInCurrentRunState(periodic)) cancel(false); else if (!periodic) super.run(); else if (super.runAndReset()) { setNextRunTime(); // 关键差异点 reExecutePeriodic(outerTask); } } private void setNextRunTime() { long p = period; if (p > 0) // scheduleAtFixedRate time += p; // 基于上次计划时间累加,保持固定频率 else // scheduleWithFixedDelay time = triggerTime(-p); // 基于当前完成时间 + delay }后果对比示例:// 假设任务执行需要 4 秒,周期/延迟设为 3 秒 // scheduleAtFixedRate (period=3 > 0) // 任务1:0秒开始,4秒结束 → 下次执行时间 = 0+3=3秒(已过),立即执行任务2 // 任务2:3秒(实际4秒)开始... // scheduleWithFixedDelay (period=-3) // 任务1:0秒开始,4秒结束 → 下次执行时间 = 当前时间+3秒 = 7秒十、线程池监控与告警体系搭建10.1 基于 Micrometer 的完整监控实现@Component public class ThreadPoolMetricsBinder { private final MeterRegistry meterRegistry; private final Map<String, ThreadPoolExecutor> pools = new ConcurrentHashMap<>(); public void register(String poolName, ThreadPoolExecutor executor) { pools.put(poolName, executor); // 核心指标 Gauge Gauge.builder("thread.pool.core.size", executor, ThreadPoolExecutor::getCorePoolSize) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.max.size", executor, ThreadPoolExecutor::getMaximumPoolSize) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.active", executor, ThreadPoolExecutor::getActiveCount) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.pool.size", executor, ThreadPoolExecutor::getPoolSize) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.largest.size", executor, ThreadPoolExecutor::getLargestPoolSize) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.queue.size", executor, e -> e.getQueue().size()) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.queue.remaining", executor, e -> e.getQueue().remainingCapacity()) .tag("pool", poolName).register(meterRegistry); Gauge.builder("thread.pool.completed", executor, e -> e.getCompletedTaskCount()) .tag("pool", poolName).register(meterRegistry); // 派生计算指标(队列使用率) Gauge.builder("thread.pool.queue.usage", executor, e -> { int size = e.getQueue().size(); int capacity = size + e.getQueue().remainingCapacity(); return capacity > 0 ? (double) size / capacity : 0; }).tag("pool", poolName).register(meterRegistry); } // 定时打印监控日志(便于开发调试) @Scheduled(fixedDelay = 30000) public void logPoolStats() { pools.forEach((name, pool) -> { log.info("线程池[{}] 状态 - 核心:{}, 当前:{}, 活跃:{}, 队列:{}/{}, 完成:{}, 拒绝:{}", name, pool.getCorePoolSize(), pool.getPoolSize(), pool.getActiveCount(), pool.getQueue().size(), pool.getQueue().size() + pool.getQueue().remainingCapacity(), pool.getCompletedTaskCount(), getRejectedCount(pool) // 需自行记录 ); }); } }10.2 拒绝计数与异常计数记录public class InstrumentedThreadPool extends ThreadPoolExecutor { private final Counter rejectedCounter; private final Counter exceptionCounter; public InstrumentedThreadPool(..., MeterRegistry registry, String poolName) { super(...); this.rejectedCounter = Counter.builder("thread.pool.rejected") .tag("pool", poolName).register(registry); this.exceptionCounter = Counter.builder("thread.pool.exception") .tag("pool", poolName).register(registry); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t != null) { exceptionCounter.increment(); } } // 自定义拒绝处理器,在其内部调用 rejectedCounter.increment() }10.3 告警规则配置(Prometheus + AlertManager 示例)groups: - name: thread_pool_alerts interval: 30s rules: - alert: ThreadPoolQueueHighUsage expr: thread_pool_queue_usage > 0.8 for: 5m annotations: summary: "线程池 {{ $labels.pool }} 队列使用率超过 80%" - alert: ThreadPoolRejectOccurred expr: increase(thread_pool_rejected_total[1m]) > 0 annotations: summary: "线程池 {{ $labels.pool }} 出现任务拒绝" - alert: ThreadPoolActiveThreadHigh expr: thread_pool_active / thread_pool_max_size > 0.9 for: 10m annotations: summary: "线程池 {{ $labels.pool }} 活跃线程数持续接近上限"十一、常见陷阱与最佳实践11.1 陷阱一:ThreadLocal 内存泄漏// ❌ 错误:在任务中设置 ThreadLocal 但未清理 executor.submit(() -> { ThreadLocal<HeavyObject> tl = new ThreadLocal<>(); tl.set(new HeavyObject()); // 任务结束后 tl 仍被线程持有 // 未调用 remove() }); // ✅ 正确:使用 try-finally 确保清理 executor.submit(() -> { ThreadLocal<HeavyObject> tl = new ThreadLocal<>(); try { tl.set(new HeavyObject()); // 业务逻辑 } finally { tl.remove(); } });11.2 陷阱二:线程池被多个任务共享时的并发问题// 线程池本身是线程安全的,但要注意: // 如果任务间共享可变状态,必须加锁 class SharedCounter { private int count = 0; public void increment() { count++; } // 非原子操作 } // ❌ 多个任务同时修改共享对象 SharedCounter counter = new SharedCounter(); for (int i = 0; i < 1000; i++) { executor.submit(counter::increment); } // 结果可能小于 1000 // ✅ 使用原子类或同步 class SafeCounter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } }11.3 陷阱三:使用无界队列导致 OOM// ❌ 致命错误 ExecutorService pool = Executors.newFixedThreadPool(10); // 队列是 LinkedBlockingQueue,默认 Integer.MAX_VALUE // 如果任务提交速度远大于处理速度,队列会无限增长直至 OOM // ✅ 使用有界队列 new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(1000), // 指定容量 ...);11.4 陷阱四:异常被吞没// submit 的异常如果不调用 get() 会被静默吞掉 Future<?> future = executor.submit(() -> { throw new RuntimeException(); }); // 这里不会打印任何异常日志 // 解决方案: // 1. 始终调用 get() 处理异常 // 2. 使用 execute 代替 submit // 3. 在 afterExecute 中捕获并记录异常(如前面所示)11.5 陷阱五:线程池关闭后仍有任务提交executor.shutdown(); executor.execute(task); // 抛出 RejectedExecutionException11.6 最佳实践清单实践项具体做法禁止 Executors 快捷方法显式使用 ThreadPoolExecutor 构造函数有界队列 + 合理拒绝策略根据业务选择 CallerRunsPolicy 或自定义自定义线程命名使用 NamedThreadFactory监控指标全覆盖集成 Micrometer,暴露关键指标优雅关闭使用两阶段关闭模板异常处理在 afterExecute 中统一记录未捕获异常上下文传递使用 TransmittableThreadLocal 或手动清理参数动态化提供配置中心调整核心参数的能力十二、生产级线程池配置模板12.1 通用业务线程池配置@Configuration public class ThreadPoolConfig { @Bean("commonExecutor") public ThreadPoolExecutor commonExecutor() { int cpuCores = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor executor = new ThreadPoolExecutor( cpuCores * 2, // corePoolSize cpuCores * 4, // maximumPoolSize 60, TimeUnit.SECONDS, // keepAlive new ArrayBlockingQueue<>(2000), // 有界队列 new NamedThreadFactory("common-pool", false, Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy() ); executor.allowCoreThreadTimeOut(true); // 空闲时回收核心线程 return executor; } }12.2 高吞吐 I/O 密集型线程池@Bean("ioIntensiveExecutor") public ThreadPoolExecutor ioIntensiveExecutor() { // 根据 I/O 等待时间调整 int coreSize = Runtime.getRuntime().availableProcessors() * 2; int maxSize = coreSize * 4; return new ThreadPoolExecutor( coreSize, maxSize, 120, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5000), // 容量较大 new NamedThreadFactory("io-pool", false, Thread.NORM_PRIORITY), new BlockingWaitPolicy(100) // 自定义阻塞等待拒绝策略 ); }12.3 定时任务专用线程池@Bean("scheduledExecutor") public ScheduledExecutorService scheduledExecutor() { return new ScheduledThreadPoolExecutor( Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("scheduled-pool", false, Thread.NORM_PRIORITY), new ThreadPoolExecutor.CallerRunsPolicy() ); }12.4 带有完整监控的线程池构建器public class MonitoredThreadPoolBuilder { public static ThreadPoolExecutor build(String poolName, int core, int max, int queueCapacity, MeterRegistry registry) { ThreadPoolExecutor executor = new InstrumentedThreadPool( core, max, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueCapacity), new NamedThreadFactory(poolName), new MonitoredCallerRunsPolicy(registry), registry, poolName ); // 注册到监控绑定器 return executor; } }附录:快速参考卡线程池参数速查表参数推荐值范围备注corePoolSizeCPU核数 ~ CPU核数×2根据任务类型调整maximumPoolSizecorePoolSize ~ corePoolSize×4不宜过大,避免上下文切换开销keepAliveTime30s ~ 120s临时线程回收时间队列容量500 ~ 10000根据任务提交速率和处理速率计算拒绝策略选择指南场景推荐策略关键业务,不可丢失CallerRunsPolicy 或持久化策略可丢弃的非核心任务DiscardPolicy新任务优先级高于旧任务DiscardOldestPolicy快速失败,开发调试AbortPolicy(默认)线程数计算公式N = CPU 核心数 U = 目标 CPU 利用率 (0 < U <= 1) W/C = 等待时间与计算时间的比值 最优线程数 = N * U * (1 + W/C)以上内容涵盖了从基础概念到源码原理、从监控告警到生产实践的完整知识体系,可作为学习和工作的全面参考资料。
2026年04月09日
9 阅读
0 评论
0 点赞
2026-04-08
性能调优实战笔记
MySQL 8.0 性能调优实战笔记(最终完整版)前言在应用开发中,MySQL 作为最流行的开源关系型数据库,其性能直接影响系统的响应速度与可用性。本笔记从实际开发中遇到的常见性能问题出发,结合 MySQL 8.0 的新特性,系统讲解性能调优的思路、方法与最佳实践。内容覆盖查询优化、增删改优化、DDL 变更、连接管理、配置调优、故障排查等全链路场景,并包含高频面试考点与生产环境避坑指南,适合初学者入门及中级开发者进阶参考。目录性能问题根源剖析查询优化篇深分页问题与解决方案多表关联与多表深分页COUNT 查询的性能陷阱ORDER BY + LIMIT 深层优化GROUP BY 优化策略DISTINCT vs GROUP BY子查询优化:派生表合并与物化UNION 与 UNION ALL 的差异SQL 通用优化技巧EXPLAIN 与 EXPLAIN ANALYZEMySQL 8.0 新特性应用索引设计最佳实践索引设计原则最左前缀与覆盖索引索引下推(ICP)与 MRRMySQL 8.0 索引新特性INSERT 操作性能调优UPDATE / DELETE 操作性能调优死锁深度解析热点更新(秒杀场景)线上大表结构变更(Online DDL)数据库连接管理与连接池调优配置参数调优监控、诊断与故障排查高频陷阱与补充专题隐式类型转换深度案例大字段(TEXT/BLOB)优化策略字符集与排序规则的影响乐观锁与悲观锁选择事务隔离级别与 Binlog 格式分区表使用场景总结与思维导图一、性能问题根源剖析1.1 MySQL 执行一条查询的流程客户端发送SQL → 连接器 → 查询缓存(8.0已弃用) → 解析器 → 优化器 → 执行器 → 存储引擎优化器基于成本(CPU 和 I/O)选择执行计划,理解这一过程是调优的基础。1.2 性能问题的主要类型问题类型典型表现常见原因慢查询单条 SQL 执行时间过长全表扫描、索引缺失、SQL 写法不佳高并发压力大量请求堆积连接数不足、锁竞争、资源不足深分页卡顿翻到几十页后响应变慢OFFSET 过大导致大量无效扫描JOIN 性能差多表关联查询超时驱动表选择错误、缺少索引、临时表过大索引失效明明有索引却不走隐式类型转换、函数操作、最左前缀不匹配写入变慢INSERT/UPDATE/DELETE 耗时高索引过多、大事务、锁等待、死锁二、查询优化篇2.1 深分页问题——从原理到解决方案2.1.1 深分页为什么慢?-- 获取第10001到10010条记录 SELECT * FROM t_order ORDER BY id LIMIT 10000, 10;当 offset 较大时,MySQL 必须从头扫描 offset + N 条记录,然后丢弃前 offset 条,导致扫描行数呈线性增长。若依赖二级索引且不满足覆盖索引,还会发生大量无意义的回表随机 I/O。2.1.2 方案一:书签分页/游标分页(最推荐)用上一页最后一条数据的主键作为“书签”直接定位。-- 优化后:假设上一页最后一条 id=100000 SELECT id, name, price FROM goods WHERE category = 1 AND id > 100000 ORDER BY id LIMIT 20;处理排序字段重复值:-- 按创建时间倒序分页 SELECT id, name, price, create_time FROM goods WHERE category = 1 AND (create_time < '2026-01-15 18:30:00' OR (create_time = '2026-01-15 18:30:00' AND id < 100000)) ORDER BY create_time DESC, id DESC LIMIT 20;2.1.3 方案二:子查询/延迟关联-- 阿里巴巴推荐写法 SELECT * FROM t_order WHERE id >= ( SELECT id FROM t_order ORDER BY id LIMIT 1000000, 1 ) ORDER BY id LIMIT 10; -- 或使用 JOIN SELECT t1.* FROM t_order t1 INNER JOIN ( SELECT id FROM t_order ORDER BY id LIMIT 100000, 10 ) t2 ON t1.id = t2.id;2.1.4 方案三:覆盖索引避免回表-- 创建覆盖索引(包含查询所需的所有字段) ALTER TABLE student ADD INDEX idx_age_name_id(age, name, id); SELECT id, age, name FROM student WHERE age = 18 LIMIT 5000, 10;2.1.5 方案四:阈值倒序法当页码超过阈值时,使用倒序查询第一页。SELECT * FROM t_order WHERE id > 0 ORDER BY id DESC LIMIT 20;2.1.6 方案五:业务层面优化限制最大页数(如100页)搜索 + 过滤结合缩小数据范围无限滚动代替跳页2.2 多表关联查询与多表深分页2.2.1 JOIN 性能陷阱复杂 JOIN 超时的主要原因是优化器选错驱动表导致全表扫描、临时表和文件排序。2.2.2 优化原则一:尽量转化为单表查询先在一张表上完成过滤和分页,再用 ID 列表去其他表补充字段。SELECT t1.*, t2.extra_field FROM tableA t1 JOIN ( SELECT id FROM tableA WHERE condition ORDER BY score DESC LIMIT 100000, 20 ) tmp ON t1.id = tmp.id LEFT JOIN tableB t2 ON t1.id = t2.a_id;2.2.3 优化原则二:小表驱动大表使用 STRAIGHT_JOIN 强制指定驱动表。SELECT * FROM small_table STRAIGHT_JOIN large_table ON small_table.id = large_table.small_id WHERE small_table.status = 1;2.2.4 多表深分页的解决方案策略一:先分页后关联(同上) 策略二:使用 CTE(MySQL 8.0)WITH paged_ids AS ( SELECT id FROM tableA WHERE condition ORDER BY id LIMIT 100000, 20 ) SELECT a.*, b.extra_field FROM paged_ids p JOIN tableA a ON p.id = a.id LEFT JOIN tableB b ON a.id = b.a_id;2.3 COUNT 查询的性能陷阱与优化2.3.1 COUNT(*) 在 InnoDB 中的实现InnoDB 没有维护表级行数计数器(因为 MVCC 下不同事务看到的行数不同),因此 SELECT COUNT(*) FROM large_table 会触发全索引扫描,大表上非常慢。2.3.2 优化策略策略一:使用近似值-- 从统计信息获取估算行数(不精确但极快) SELECT TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'db' AND TABLE_NAME = 'orders';策略二:使用计数表CREATE TABLE order_stat ( date DATE PRIMARY KEY, cnt INT NOT NULL ) ENGINE=InnoDB; -- 插入订单时原子更新 INSERT INTO orders VALUES (...); INSERT INTO order_stat VALUES (CURDATE(), 1) ON DUPLICATE KEY UPDATE cnt = cnt + 1;策略三:Redis 计数器(适用于高实时性场景) 策略四:覆盖索引加速 COUNTCREATE INDEX idx_create_time ON orders(create_time); SELECT COUNT(*) FROM orders WHERE create_time >= '2026-01-01';2.3.3 COUNT(1) vs COUNT(*) vs COUNT(列)COUNT(*) 和 COUNT(1) 性能完全相同,优化器会转换为 COUNT(*)COUNT(col) 统计该列非 NULL 的行数2.4 ORDER BY + LIMIT 的深层优化2.4.1 排序缓冲区与优先队列当排序无法使用索引时,MySQL 使用 sort_buffer_size 内存排序,超出则使用磁盘文件。对于带 LIMIT 的排序,MySQL 5.6+ 采用优先队列(Priority Queue)算法,只需维护一个大小为 N 的堆,无需全量排序,大幅降低内存和磁盘使用。2.4.2 监控排序状态SHOW STATUS LIKE '%sort%';Sort_merge_passes:外部归并排序次数(应尽量为0)Sort_range:范围扫描排序次数Sort_scan:全表扫描排序次数若 Sort_merge_passes 持续增长,可适当增大 sort_buffer_size。2.5 GROUP BY 优化策略2.5.1 松散索引扫描(Loose Index Scan)当 GROUP BY 列是索引最左前缀且 SELECT 仅包含 MIN/MAX 聚合函数时,MySQL 可仅扫描分组的第一行,性能极佳。2.5.2 避免临时表和文件排序EXPLAIN 中出现 Using temporary; Using filesort 是需要优化的信号。优化方法:创建满足 GROUP BY 和 ORDER BY 的复合索引,或使用窗口函数替代复杂分组子查询。2.6 DISTINCT 与 GROUP BY 的选择单列去重:两者性能相近多列去重:GROUP BY 可能更快(可利用松散索引扫描)DISTINCT 会产生临时表做去重,大数据量时性能较差建议:多列去重优先考虑 GROUP BY,并确保索引覆盖。2.7 子查询优化:派生表合并与物化MySQL 8.0 对子查询优化显著增强:派生表合并:优化器尝试将 FROM 子句中的子查询展开并与外层合并,减少临时表。物化:无法合并时,子查询结果存入临时表并自动创建索引加速 JOIN。建议:能用 JOIN 解决的问题尽量用 JOIN,避免依赖优化器“黑盒”行为。2.8 UNION 与 UNION ALL 的性能差异UNION ALL:直接合并结果集,不去重UNION:需额外临时表和排序去重,大数据量下比 UNION ALL 慢数倍规则:除非确实需要去重,否则一律使用 UNION ALL。2.9 SQL 语句通用优化技巧2.9.1 避免索引失效情况示例修复隐式类型转换WHERE phone = 13800138000WHERE phone = '13800138000'函数操作WHERE YEAR(create_time) = 2023WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31'最左前缀不匹配索引 (a,b,c),查询 WHERE b=1确保包含最左列OR 条件WHERE a=1 OR b=2改为 UNION 或加索引LIKE 前缀通配WHERE name LIKE '%张'避免前缀%,或使用全文索引2.9.2 其他技巧只查询需要的字段,避免 SELECT *分页时先获取主键再关联详情合理使用 IN 和 EXISTS(EXISTS 通常更快)2.10 EXPLAIN 与 EXPLAIN ANALYZE2.10.1 EXPLAIN 关键字段字段含义理想值type访问类型const > ref > range > index > ALLpossible_keys可能使用的索引不为空key实际使用的索引与期望一致rows预估扫描行数越小越好Extra额外信息Using index(覆盖索引)优于 Using filesort2.10.2 EXPLAIN ANALYZE(MySQL 8.0.18+)真实执行查询并报告每个操作的实际时间和行数。EXPLAIN ANALYZE SELECT u.name, o.order_no FROM users u JOIN orders o ON u.id = o.user_id WHERE u.age > 25;2.11 MySQL 8.0 新特性应用2.11.1 窗口函数-- 获取每个用户的最新订单 WITH ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC) AS rn FROM user_orders ) SELECT * FROM ranked WHERE rn = 1;2.11.2 公用表表达式(CTE)WITH active_users AS ( SELECT id, name FROM users WHERE status = 'active' ), user_orders AS ( SELECT u.id, COUNT(o.id) AS order_count FROM active_users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id ) SELECT * FROM user_orders WHERE order_count > 10;三、索引设计最佳实践3.1 索引设计原则应该创建索引的列:WHERE 子句中的过滤条件列ORDER BY / GROUP BY 的列JOIN 关联字段不应该创建索引的列:低选择性的列(区分度低于10%)高频更新的列大字段(TEXT、BLOB)3.2 最左前缀与覆盖索引组合索引遵循最左前缀匹配,创建时需考虑查询模式。CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time, id, order_no, amount);覆盖索引:查询所需字段全部包含在索引中,无需回表。3.3 索引下推(ICP)与 Multi-Range Read(MRR)3.3.1 索引下推(ICP)MySQL 5.6+ 默认开启。使用二级索引时,将索引列条件直接下推到存储引擎过滤,减少回表次数。EXPLAIN 中 Extra 显示 Using index condition。3.3.2 MRR(Multi-Range Read)将二级索引回表时的随机 I/O 优化为顺序 I/O。通过 optimizer_switch 控制。SET optimizer_switch='mrr=on,mrr_cost_based=off';3.4 MySQL 8.0 索引新特性特性说明不可见索引ALTER TABLE t ALTER INDEX idx_name INVISIBLE; 可临时禁用而不删除降序索引CREATE INDEX idx ON t(col1 ASC, col2 DESC); 真正支持降序函数索引CREATE INDEX idx ON t((YEAR(create_date))); 对函数结果建索引四、INSERT 操作性能调优4.1 插入操作的成本构成官方数据显示:单条插入的固定开销(连接、解析等)远大于数据写入本身。索引维护也是主要开销之一。4.2 批量插入:最有效的优化手段-- ❌ 单条循环插入 INSERT INTO orders(user_id, amount) VALUES (1, 100); -- ✅ 多值批量插入(每批 500-1000 条) INSERT INTO orders(user_id, amount) VALUES (1, 100), (2, 200), (3, 300), ...;4.3 LOAD DATA:最快的数据导入方式LOAD DATA LOCAL INFILE '/path/to/data.csv' INTO TABLE orders FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n' (user_id, amount, create_time);4.4 事务优化:用 COMMIT 包裹批量操作SET autocommit = 0; -- 批量插入 COMMIT; SET autocommit = 1;4.5 按主键顺序插入(InnoDB 特有)主键顺序插入可大幅减少页分裂,提升性能。4.6 临时关闭约束检查(数据迁移场景)SET unique_checks = 0; SET foreign_key_checks = 0; -- 执行大量 INSERT SET unique_checks = 1; SET foreign_key_checks = 1;警告:仅在数据完整性已由业务保证时使用。4.7 自增 ID 的批量申请机制MySQL 8.0 批量插入时会一次性申请一批连续 ID,导致 ID 可能出现“断崖”,这是正常优化,不建议强行修复。4.8 INSERT 优化速查表优化方法性能提升幅度注意事项批量 VALUES5-20 倍每批 500-1000 条LOAD DATA约 20 倍需 FILE 权限事务包裹3-10 倍注意事务大小主键顺序插入2-5 倍预先排序数据关闭约束检查3-8 倍确保数据完整性五、UPDATE / DELETE 操作性能调优5.1 无索引导致全表锁(严重事故)-- ❌ 危险:user_id 没有索引 UPDATE orders SET status = 'expired' WHERE user_id = 100; -- 结果:全表扫描 → 对所有行加锁 → 整个表被锁死必须确保 WHERE 条件列有索引。5.2 批量删除的正确姿势-- ✅ 安全:分批删除 DELETE FROM logs WHERE create_time < '2025-01-01' LIMIT 1000; -- 循环执行,每批之间 sleep 一下5.3 死锁问题深度解析5.3.1 死锁原因:锁顺序不一致-- 事务A:先订单后库存 BEGIN; UPDATE orders SET status = 'paid' WHERE id = 100; UPDATE inventory SET stock = stock - 1 WHERE product_id = 200; COMMIT; -- 事务B:先库存后订单(相反顺序!) BEGIN; UPDATE inventory SET stock = stock - 1 WHERE product_id = 200; UPDATE orders SET status = 'paid' WHERE id = 100; COMMIT;5.3.2 诊断死锁开启死锁日志:innodb_print_all_deadlocks = ON查看锁状态:SHOW ENGINE INNODB STATUS\G5.3.3 避免死锁的策略统一加锁顺序:所有事务按相同顺序访问表尽量使用主键更新:减少辅助索引上的锁竞争缩短事务时间:把非数据库操作移出事务使用 SKIP LOCKED / NOWAIT(MySQL 8.0)SELECT * FROM tasks WHERE status = 'pending' FOR UPDATE SKIP LOCKED LIMIT 10;5.4 热点更新问题(秒杀场景)5.4.1 应用层方案Redis 预扣库存 + 异步落库消息队列削峰排队机制5.4.2 数据库层方案云厂商的热点更新功能可自动探测热点行并排队执行。5.5 MySQL 8.0 批量更新:CTE 方式WITH tmp AS ( SELECT 'new_name_1' AS name, 'id_1' AS id UNION ALL SELECT 'new_name_2' AS name, 'id_2' AS id ) UPDATE users, tmp SET users.name = tmp.name WHERE users.id = tmp.id;5.6 UPDATE/DELETE 优化速查表优化方法适用场景注意事项WHERE 条件列加索引所有 UPDATE/DELETE避免全表扫描锁全表分批删除(LIMIT)大量数据删除每批 500-1000 条,适当 sleep统一加锁顺序多表更新场景所有事务相同顺序基于主键更新避免死锁优先使用主键CTE 批量更新批量更新不同值每批 1000 条以内六、线上大表结构变更(Online DDL)6.1 三种 DDL 算法对比算法DML 允许执行时间额外空间适用场景COPY❌ 阻塞写入非常长大已淘汰INPLACE✅ 允许长小不支持 INSTANT 的操作INSTANT✅ 允许秒级极小添加列、设置默认值等(8.0.12+)6.2 安全执行 DDL 的标准语法ALTER TABLE user ADD COLUMN age INT NOT NULL DEFAULT 0, ALGORITHM = INSTANT, LOCK = NONE;6.3 gh-ost:大表变更的终极方案对于不支持 INSTANT 的大表 DDL,推荐使用 gh-ost(GitHub Online Schema Migration),它基于 binlog 回放增量数据,无触发器,可暂停、可限流、原子切换。6.4 DDL 与 MDL 锁DDL 需要获取 MDL 写锁,若存在长事务未提交,DDL 会阻塞后续所有 DML。操作前应检查长事务:SELECT * FROM information_schema.innodb_trx WHERE trx_started < NOW() - INTERVAL 10 MINUTE;七、数据库连接管理与连接池调优7.1 连接数爆满问题原因:连接池配置过大、慢查询堆积、连接泄漏、瞬时流量高峰。解决方案:合理配置 max_connections 与连接池大小。[mysqld] max_connections = 500 thread_cache_size = 32 table_open_cache = 2000 open_files_limit = 100007.2 连接池“假死”与空闲连接回收连接池的空闲超时时间必须小于数据库的 wait_timeout。Druid 保活配置:spring: datasource: druid: test-while-idle: true validation-query: SELECT 1 time-between-eviction-runs-millis: 30000 min-evictable-idle-time-millis: 180000HikariCP 配置:spring: datasource: hikari: idle-timeout: 180000 max-lifetime: 600000 keepalive-time: 30000 # MySQL 8.0 特有7.3 MySQL 8.0 驱动层注意事项高可靠 URL 模板:jdbc:mysql://host:3306/db?useSSL=false&serverTimezone=Asia/Shanghai&tcpKeepAlive=true&allowPublicKeyRetrieval=true八、配置参数调优8.1 关键参数配置[mysqld] # InnoDB 缓冲池大小(物理内存的 70%-80%) innodb_buffer_pool_size = 6G # 重做日志大小(缓冲池的 25% 左右) innodb_log_file_size = 1536M # 日志刷新策略(2=每秒刷新,平衡方案) innodb_flush_log_at_trx_commit = 2 # 最大连接数 max_connections = 500 # 线程缓存 thread_cache_size = 32 # 排序缓冲区(会话级) sort_buffer_size = 2M # 连接缓冲区 join_buffer_size = 2M8.2 缓冲池命中率监控SHOW STATUS LIKE 'Innodb_buffer_pool_read%'; -- 命中率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) -- 目标 > 99%8.3 MySQL 8.0 优化器参数prefer_ordering_index:控制优化器对排序索引的偏好,可适时关闭。SET SESSION optimizer_switch='prefer_ordering_index=OFF';九、监控、诊断与故障排查9.1 慢查询日志[mysqld] slow_query_log = 1 long_query_time = 1.0 log_queries_not_using_indexes = 1 log_slow_extra = 1 # MySQL 8.0.14+,记录更多信息分析工具:mysqldumpslowpt-query-digest9.2 Performance Schema 与 Sys Schema-- 查看最耗时语句 SELECT * FROM sys.statement_analysis LIMIT 10; -- 查看冗余索引 SELECT * FROM sys.schema_redundant_indexes; -- 查看未使用的索引 SELECT * FROM sys.schema_unused_indexes;9.3 故障排查流程图9.3.1 写入操作慢排查1. 检查是否是单条操作 → 改用批量操作 2. 检查 WHERE 条件是否有索引 → 加索引 3. 检查是否被锁阻塞 → SHOW ENGINE INNODB STATUS 4. 检查是否有死锁 → 查看错误日志 5. 检查配置参数(autocommit、buffer_pool_size) 6. 检查磁盘 I/O(iostat)9.3.2 连接数爆满排查1. SHOW PROCESSLIST → 查看连接和 SQL 2. 检查是否有慢查询堆积 → 优化慢 SQL 3. 检查是否有连接泄漏 → 查看 Sleep 连接时长 4. 临时调高 max_connections 应急 5. 调优应用连接池配置十、高频陷阱与补充专题10.1 隐式类型转换深度案例10.1.1 字符串与数字比较-- 表定义 phone VARCHAR(20) 有索引 SELECT * FROM users WHERE phone = 13800138000; -- 索引失效 SELECT * FROM users WHERE phone = '13800138000'; -- 走索引10.1.2 字符集转换导致索引失效连接条件中字符集不一致会导致索引失效,应统一表字符集。10.1.3 日期字符串比较SELECT * FROM t WHERE date_col = '2026-04-08'; -- 走索引 SELECT * FROM t WHERE date_col = STR_TO_DATE('2026-04-08','%Y-%m-%d'); -- 索引失效10.2 大字段(TEXT/BLOB)优化策略10.2.1 InnoDB 行溢出机制大字段数据存储在独立溢出页,扫描时可能产生大量随机 I/O。10.2.2 优化手段垂直拆分:将大字段分离到扩展表外部存储:URL 存 MySQL,文件存 OSS调整行格式:ROW_FORMAT=COMPRESSED(增加 CPU 开销)10.3 字符集与排序规则的影响统一数据库、表、列的字符集和排序规则避免在查询中使用 COLLATE 子句(导致索引失效)utf8mb4 下索引长度计算:VARCHAR(255) 最大 1020 字节10.4 乐观锁与悲观锁的选择10.4.1 悲观锁SELECT * FROM inventory WHERE id = 1 FOR UPDATE; UPDATE inventory SET stock = stock - 1 WHERE id = 1;特点:强一致性,并发低。10.4.2 乐观锁UPDATE inventory SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = old_version;特点:高并发,需处理重试。选择依据:冲突概率高用悲观锁,概率低用乐观锁。10.5 事务隔离级别与 Binlog 格式10.5.1 隔离级别选择隔离级别性能影响适用场景READ COMMITTED较低OLTP 推荐,配合 ROW 格式 binlogREPEATABLE READ(默认)中等,间隙锁较多一致性要求高SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;10.5.2 Binlog 格式MySQL 8.0 推荐 ROW 格式,配合 binlog_row_image=MINIMAL 减少日志量。10.6 分区表使用场景适合按时间范围查询的大表(如日志表),可裁剪分区加速查询,归档历史数据秒级完成。但分区表不支持外键,分区数不宜过多。CREATE TABLE logs ( id INT, log_date DATE, content TEXT ) PARTITION BY RANGE (YEAR(log_date)) ( PARTITION p2024 VALUES LESS THAN (2025), PARTITION p2025 VALUES LESS THAN (2026) );十一、总结与思维导图11.1 调优方法论发现问题:通过监控、慢查询日志定位瓶颈分析问题:使用 EXPLAIN/EXPLAIN ANALYZE 分析执行计划制定方案:从索引、SQL、配置、架构四个层面考虑实施优化:一次只改一个点,便于评估验证效果:对比优化前后性能指标持续监控:定期检查,防止性能退化11.2 核心要点速记深分页用书签分页,不要直接用大 OFFSET多表 JOIN 尽量转单表查询,先分页后关联WHERE 条件列必须有索引,避免全表锁批量操作代替逐条操作,控制事务大小死锁源于锁顺序不一致,统一加锁顺序DDL 优先使用 INSTANT,不行再用 gh-ost连接池空闲超时 < wait_timeoutinnodb_buffer_pool_size 设内存的 70%,最重要慢查询日志必开,定期分析优化11.3 调优维度总结维度主要手段效果SQL 语句层改写查询、游标分页、避免 SELECT *立竿见影索引层创建合适索引、覆盖索引、清理冗余索引长期有效配置参数层调整缓冲池、连接数、日志策略整体提升架构层读写分离、分库分表、引入缓存从根本上解决监控运维层慢日志分析、定期巡检、容量规划防患于未然使用建议 本笔记基于 MySQL 8.0 + InnoDB 存储引擎,结合生产环境实战经验整理,覆盖了 90% 以上的日常性能问题场景。建议读者在实际工作中:遇到性能问题时先查阅对应章节,理解原理后再动手优化;所有优化操作先在测试环境充分验证;调优是持续过程,养成定期分析慢日志和监控指标的习惯。 祝你在 MySQL 调优之路上越走越远,系统永远稳定高效!
2026年04月08日
7 阅读
0 评论
1 点赞
2026-04-03
JavaScript 核心知识笔记
JavaScript 核心知识笔记本文档整合了 Promise 异步、数组/对象核心方法、函数式编程、异步并发控制、不可变数据更新等企业级必备技巧。内容全面,示例丰富,可直接用于开发参考。一、Promise 与异步基础1.1 Promise 核心// 创建异步任务 new Promise((resolve, reject) => { /* 异步操作 */ }); // 处理结果 promise.then(onFulfilled).catch(onRejected); // 快速创建已决议的 Promise Promise.resolve(value); Promise.reject(error); // 并发控制 Promise.all([p1, p2]) // 全部成功才成功,返回结果数组 Promise.race([p1, p2]) // 第一个完成的决定结果 Promise.allSettled() // 等待全部结束,返回每个的状态 Promise.any() // 第一个成功的决定结果1.2 async/await 优雅语法async function fetchData() { try { const result = await somePromise(); // 直接拿到结果,不用写 then } catch (err) { // 错误处理 } } // await 就是等 Promise 干完活,直接拿结果二、数组核心处理函数(必会)2.1 every – 全真才为真// 表单校验:所有字段都通过校验 const formFields = [ { name: 'username', valid: true }, { name: 'email', valid: true }, { name: 'phone', valid: false } ]; const isFormValid = formFields.every(field => field.valid); // 场景:整体校验、权限全满足、批量数据合规检查2.2 some – 一真即为真const tasks = [ { id: 1, name: '写文档', done: true }, { id: 2, name: '改Bug', done: false } ]; const hasPendingTask = tasks.some(task => !task.done); // true // 搜索命中 const keywords = '张三'; const hasMatch = users.some(user => user.name.includes(keywords));2.3 filter – 筛选过滤(生成新数组)const strings = ["苹果", "", "香蕉", " ", "橙子", null]; const validStrings = strings.filter(s => s && s.trim()); // 场景:数据筛选、清理无效项、数据分类2.4 map – 一对一映射// 字符串转数字 const strNums = ['1', '2', '3']; const numbers = strNums.map(Number); // [1,2,3] // 接口字段适配 const apiData = [{ user_name: '张三', user_age: 25 }]; const frontData = apiData.map(item => ({ name: item.user_name, age: item.user_age }));2.5 reduce – 万能累加器核心:把多个元素汇总成一个结果。必须返回累加器,必须传初始值!// 求和 const sum = [1,2,3,4].reduce((acc, curr) => acc + curr, 0); // 分组统计 const orderItems = [ { category: '食品', price: 20 }, { category: '服装', price: 100 }, { category: '食品', price: 30 } ]; const totalByCategory = orderItems.reduce((acc, item) => { acc[item.category] = (acc[item.category] || 0) + item.price; return acc; }, {}); // ← 初始值必须是 {} // 场景:聚合统计、数据分组、数组转对象、函数式管道2.6 find / findIndex – 精准查找const users = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }]; const user = users.find(u => u.id === 2); // 返回元素 const index = users.findIndex(u => u.id === 2); // 返回索引,找不到 -1 // 与 indexOf 区别:支持复杂条件判断2.7 flat & flatMap – 数组扁平化const nested = [1, [2, [3, [4]]], 5]; nested.flat(Infinity); // [1,2,3,4,5] // flatMap = map + flat(1) const lines = ["Hello world", "JavaScript is good"]; const words = lines.flatMap(line => line.split(' ')); // ["Hello", "world", "JavaScript", "is", "good"]2.8 includes – 是否包含const roles = ['admin', 'editor']; const isAdmin = roles.includes('admin');2.9 sort – 排序(会修改原数组!)const nums = [3,1,2]; // 不修改原数组的安全写法 const sorted = [...nums].sort((a,b) => a - b); // 升序 const desc = [...nums].sort((a,b) => b - a); // 降序2.10 reverse – 反转(会修改原数组)const arr = [1,2,3]; const reversed = [...arr].reverse(); // [3,2,1]2.11 slice – 截取(不改变原数组)const arr = [1,2,3,4]; arr.slice(1); // [2,3,4] 从索引1开始 arr.slice(1,3); // [2,3]2.12 splice – 删除/插入/替换(改变原数组)const arr = [1,2,3]; arr.splice(1, 1); // 从索引1删除1个 → [1,3] arr.splice(1, 0, 99); // 插入 → [1,99,3]三、对象核心处理函数3.1 遍历三剑客:keys / values / entriesconst obj = { a:1, b:2, c:3 }; Object.keys(obj); // ['a','b','c'] Object.values(obj); // [1,2,3] Object.entries(obj); // [['a',1], ['b',2], ['c',3]] // 遍历替代 for...in,方便使用数组方法 Object.entries(obj).forEach(([key, value]) => { console.log(key, value); });3.2 Object.fromEntries – 键值对转回对象// 过滤对象属性(只保留 name 和 age) const obj = { name: '张三', age: 25, gender: '男' }; const picked = Object.fromEntries( Object.entries(obj).filter(([k]) => ['name','age'].includes(k)) ); // { name:'张三', age:25 } // 对象转 URL 参数 const query = { name: '张三', age: 25 }; const search = new URLSearchParams(query).toString(); // name=张三&age=253.3 Object.assign – 浅拷贝与合并const obj1 = { a:1 }; const obj2 = { b:2 }; const merged = Object.assign({}, obj1, obj2); // { a:1, b:2 } // 等价于 { ...obj1, ...obj2 }3.4 可选链 ?. 与空值合并 ??// 安全访问深层属性 const user = { profile: { name: '张三' } }; console.log(user?.profile?.name); // '张三' console.log(user?.address?.city); // undefined(不报错) // 空值合并:只有 null/undefined 才用默认值 const count = 0; console.log(count ?? 10); // 0(不是 null/undefined,保留0) console.log(count || 10); // 10(因为0是假值,不推荐)四、函数式编程高级技巧4.1 函数组合(Pipe / Compose)将多个函数串联成数据流水线,代码线性、可读性强。// 从左到右执行(pipe,更符合人类阅读习惯) const pipe = (...fns) => (initValue) => fns.reduce((acc, fn) => fn(acc), initValue); // 从右到左执行(compose,经典函数式) const compose = (...fns) => (initValue) => fns.reduceRight((acc, fn) => fn(acc), initValue); // 实战:处理商品数据 const filterValid = (list) => list.filter(item => item.price > 0 && item.status === 1); const mapField = (list) => list.map(({ goods_id: id, goods_name: name, price }) => ({ id, name, price })); const sortByPrice = (list) => [...list].sort((a, b) => a.price - b.price); const groupByPriceRange = (list) => list.reduce((acc, item) => { const range = item.price < 100 ? '低价' : item.price < 500 ? '中价' : '高价'; acc[range] = [...(acc[range] || []), item]; return acc; }, {}); const processGoodsData = pipe(filterValid, mapField, sortByPrice, groupByPriceRange); const result = processGoodsData(apiData);4.2 柯里化(Currying)将多参数函数拆分成单参数函数链,实现参数预置。const curry = (fn) => (...args) => args.length >= fn.length ? fn(...args) : curry(fn).bind(null, ...args); // 用法示例 const add = (a, b, c) => a + b + c; const curriedAdd = curry(add); const add5 = curriedAdd(5); console.log(add5(3, 2)); // 10 console.log(curriedAdd(1)(2)(3)); // 6五、异步并发控制(手写限流调度器)解决批量请求并发过高导致崩溃的问题。类似银行柜台:同时最多 limit 个任务在处理,其余排队。/** * 异步并发限流 * @param {Array<Function>} tasks 任务函数数组(每个任务返回 Promise) * @param {number} limit 最大并发数 * @returns {Promise<any[]>} 所有任务结果数组(顺序与原任务一致) */ const asyncPool = async (tasks, limit = 3) => { const results = []; // 存储所有结果(保持顺序) const running = []; // 存储正在执行的任务 Promise for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; // 包装任务,确保它是一个 Promise const promise = Promise.resolve().then(() => task()); results.push(promise); // 占位,顺序不变 // 任务完成后从 running 中移除 const finishPromise = promise.then(() => { const index = running.indexOf(finishPromise); if (index !== -1) running.splice(index, 1); }); running.push(finishPromise); // 当并发数达到上限,等待任意一个任务完成 if (running.length >= limit) { await Promise.race(running); } } // 等待所有任务完成 return Promise.all(results); }; // 使用示例 const createTask = (id, delay) => () => new Promise(resolve => { setTimeout(() => { console.log(`任务${id}完成`); resolve(id); }, delay); }); const tasks = [ createTask(1, 1000), createTask(2, 500), createTask(3, 800), createTask(4, 200), createTask(5, 600) ]; asyncPool(tasks, 2).then(results => console.log('全部结果', results)); // 同时最多2个任务在执行六、复杂数据处理(树形结构)6.1 扁平数据转树形(菜单/部门)// 扁平数组,每个元素有 id, parentId const flatList = [ { id: 1, parentId: null, name: '根节点' }, { id: 2, parentId: 1, name: '子节点A' }, { id: 3, parentId: 1, name: '子节点B' }, { id: 4, parentId: 2, name: '孙节点A1' } ]; function listToTree(list, parentId = null) { return list .filter(item => item.parentId === parentId) .map(item => ({ ...item, children: listToTree(list, item.id) })); } const tree = listToTree(flatList);6.2 树形数据过滤(保留符合条件的节点及其祖先)function filterTree(tree, predicate) { return tree .filter(node => predicate(node) || (node.children && filterTree(node.children, predicate).length)) .map(node => ({ ...node, children: node.children ? filterTree(node.children, predicate) : [] })); }6.3 树形查找(findNode)function findNode(tree, id) { for (const node of tree) { if (node.id === id) return node; if (node.children) { const found = findNode(node.children, id); if (found) return found; } } return null; }七、性能优化与不可变数据7.1 记忆化(Memoization)缓存函数执行结果,相同参数直接返回缓存,避免重复计算。function memoize(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn.apply(this, args); cache.set(key, result); return result; }; } // 示例:斐波那契数列 const fib = memoize(n => { if (n < 2) return n; return fib(n - 1) + fib(n - 2); }); console.log(fib(40)); // 速度极快7.2 不可变数据更新(Immutable)铁律:绝不修改原始对象/数组,所有修改都生成全新的对象/数组。// 对象深层属性更新 const state = { user: { name: '张三', age: 25 }, loading: false }; const newState = { ...state, user: { ...state.user, age: 26 } // 只修改 age }; // 数组修改(map) const todos = [{ id: 1, done: false }, { id: 2, done: true }]; const toggled = todos.map(todo => todo.id === 1 ? { ...todo, done: true } : todo ); // 数组删除(filter) const withoutId1 = todos.filter(todo => todo.id !== 1); // 数组新增(展开运算符) const newTodo = { id: 3, done: false }; const added = [...todos, newTodo]; // 总结: // - 改深层对象 → 用 ... 逐层展开 // - 改数组元素 → 用 map // - 删数组元素 → 用 filter // - 加数组元素 → 用 [...arr, newItem]7.3 深拷贝方法// 方法1:structuredClone(现代浏览器/Node 17+) const clone = structuredClone(originalObject); // 方法2:JSON 序列化(仅限纯数据,不含函数/undefined/循环引用) const clone = JSON.parse(JSON.stringify(original)); // 方法3:Lodash(工业级最稳) // const clone = _.cloneDeep(original);八、全局错误捕获与边界8.1 未捕获的 Promise 错误window.addEventListener('unhandledrejection', event => { console.error('未处理的 Promise 错误:', event.reason); // 可上报日志或提示用户 });8.2 同步代码错误捕获window.addEventListener('error', event => { console.error('全局错误:', event.error); // 避免默认行为(如控制台报错仍保留) // return true 可阻止默认处理 });8.3 异步函数的错误边界包装// 高阶函数,自动捕获 async 函数错误 const safeAsync = (fn) => async (...args) => { try { return await fn(...args); } catch (err) { console.error('异步函数错误:', err); // 可返回默认值或 throw return null; } }; // 使用 const fetchUser = safeAsync(async (id) => { const res = await fetch(`/user/${id}`); return res.json(); });九、常用实战代码片段集锦9.1 数组去重const unique = [...new Set([1,2,2,3])]; // [1,2,3]9.2 对象数组按字段去重const uniqueByKey = (arr, key) => { const map = new Map(); return arr.filter(item => !map.has(item[key]) && map.set(item[key], true)); };9.3 批量重命名字段(解构 + 剩余运算符)const renameKeys = (obj, keyMap) => Object.fromEntries( Object.entries(obj).map(([k, v]) => [keyMap[k] || k, v]) ); // 示例 const user = { user_id: 1, user_name: '张三' }; const renamed = renameKeys(user, { user_id: 'id', user_name: 'name' }); // { id:1, name:'张三' }9.4 防抖(Debounce)与节流(Throttle)// 防抖:连续触发只执行最后一次(搜索输入) function debounce(fn, delay) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } // 节流:固定间隔执行一次(滚动/resize) function throttle(fn, interval) { let last = 0; return function(...args) { const now = Date.now(); if (now - last >= interval) { last = now; fn.apply(this, args); } }; }十、总结速查表需求方法判断全部满足every判断至少一个满足some筛选数据filter转换数据map查找第一个元素find / findIndex汇总统计reduce数组去重[...new Set(arr)]深拷贝structuredClone / JSON / lodash对象遍历keys / values / entries安全访问嵌套属性?.空值默认??函数组合流水线pipe / compose参数预置curry异步限流手写 asyncPool扁平转树递归 filter + map不可变更新... 展开、map、filter性能缓存memoize最后叮嘱:Promise 是异步的地基,async/await 是漂亮的房子;数组/对象方法是日常开发的利剑;函数式组合让代码像诗一样流畅。熟记这些,足以应对大部分企业级开发场景。
2026年04月03日
13 阅读
0 评论
0 点赞
1
...
3
4
5
...
8