CompletableFuture实战避坑指南:线程池、异常与性能调优
1. 从一次接口聚合的线上事故说起去年冬天的一个凌晨我被一通告警电话叫醒。某个核心聚合接口的响应时间从平均 200ms 飙升到了 8 秒上游服务大面积超时。爬起来排查后发现问题出在一段看起来很优雅的异步代码上——一位同事用CompletableFuture把三个下游调用串了起来但其中一个thenApply里做了一次阻塞的数据库查询而这个thenApply恰好跑在了默认的ForkJoinPool.commonPool()上。当并发量上来之后公共线程池被这些阻塞任务占满整条异步链路全部卡死。这个事故让我意识到CompletableFuture这个 API 用起来门槛很低但真正用对、用好需要理解的东西远比表面上看到的要多。它不只是一个把回调写成链式的工具背后牵扯到线程池模型、任务编排语义、异常传播机制、超时控制、上下文传递等一系列工程问题。这篇文章就是把这些年我在实际项目里踩过的坑、总结的经验系统地梳理一遍。无论你是刚接触CompletableFuture的新手还是已经用了一段时间但总觉得哪里不太对的开发者应该都能从中找到对自己有用的东西。文章会从最基础的使用场景讲起逐步深入到线程池选型、组合编排、异常处理、超时与取消、上下文透传、性能调优等实战话题。每一部分我都会尽量说清楚为什么这么做以及不这么做会怎样而不是只丢一段代码让你抄。2. CompletableFuture 到底解决了什么问题2.1 从 Future 的痛处讲起在CompletableFuture出现之前Java 里做异步任务主要靠Future。Future的问题非常明显你提交一个任务拿到一个Future然后只能干等着调用get()的时候线程就阻塞在那里。更难受的是Future没有任何回调机制你没法说等这个任务完成了帮我自动执行下一步。多个Future之间也没法组合想让 A 和 B 都完成后再做 C只能自己写一堆while循环去轮询isDone()。我见过不少老项目里为了等三个并行任务的结果代码写成这样先提交三个任务然后依次get()第一个get()阻塞的时候后面两个其实早就跑完了但你还是得一个个等。这种写法在延迟敏感的场景下非常吃亏因为总耗时等于最慢的那个任务而不是三个任务并行的时间。CompletableFuture的核心价值就在于两点一是支持回调式的链式编排任务完成后自动触发下一步不需要你手动阻塞等待二是支持多任务的组合可以表达全部完成任意一个完成依赖前一个结果等复杂的依赖关系。这两点结合起来让异步编程从手动管理线程和等待变成了声明式地描述任务之间的依赖。2.2 一个最直观的对比假设有三个下游服务调用每个耗时 300ms需要把三个结果合并返回。用传统Future的写法如果串行调用就是 900ms即使用线程池并行提交最后还是要get()三次代码里充满了try-catch和阻塞点。而用CompletableFuture可以这样写CompletableFutureString f1 CompletableFuture.supplyAsync(() - callServiceA()); CompletableFutureString f2 CompletableFuture.supplyAsync(() - callServiceB()); CompletableFutureString f3 CompletableFuture.supplyAsync(() - callServiceC()); CompletableFutureString combined f1.thenCombine(f2, (a, b) - a b) .thenCombine(f3, (ab, c) - ab c); String result combined.join();三个任务并行执行总耗时接近 300ms而且依赖关系一目了然。这就是CompletableFuture最朴素的威力。但请注意这段代码里我用了默认线程池这正是前面事故的根源。后面会专门讲线程池的问题。2.3 它不是什么有一点需要澄清CompletableFuture不是银弹它不会自动让你的代码变快。它只是一个编排工具真正干活的是背后的线程池。如果你的任务本身是 CPU 密集型的或者线程池配置不合理用CompletableFuture反而可能因为线程切换和上下文开销让性能更差。我见过有人把一段纯计算的循环拆成十几个CompletableFuture并行跑结果比串行还慢就是因为任务粒度太细调度开销超过了并行收益。所以理解CompletableFuture的第一步是把它定位成任务编排框架而不是性能优化工具。它的价值在于表达复杂的异步依赖关系让代码更清晰、更可控。3. 线程池选型默认池是最大的陷阱3.1 commonPool 到底能不能用CompletableFuture的大部分方法如果不传Executor都会使用ForkJoinPool.commonPool()。这个池子有几个特点它的默认并行度是CPU 核心数 - 1所有没指定线程池的CompletableFuture任务共享它而且它是 JVM 全局的。这意味着什么意味着你的业务代码、你依赖的第三方库、框架内部的异步任务可能都在抢同一个池子。ForkJoinPool的设计初衷是处理 CPU 密集型的、可以拆分的计算任务分治算法它的工作窃取机制在这种场景下效率很高。但如果你往里丢的是阻塞任务——比如 HTTP 调用、数据库查询、文件 IO——那问题就大了。阻塞任务会占住工作线程而commonPool的线程数又少很快就会耗尽导致后续任务排队甚至饿死。这就是我开头那个事故的根本原因。结论很明确任何涉及 IO 或阻塞的异步任务都不要用默认的 commonPool必须传自己的线程池。3.2 怎么配一个合适的线程池那自己配线程池参数怎么定这取决于任务类型。对于 IO 密集型任务线程数可以设得比 CPU 核心数大很多因为线程大部分时间在等待。一个常用的经验公式是线程数 CPU 核心数 * (1 等待时间 / 计算时间)。比如一个任务里 90% 的时间在等网络返回那等待/计算比就是 9线程数可以设成核心数的 10 倍左右。但公式只是起点实际值要靠压测调。我一般会这样操作先按公式估一个值然后在压测环境里逐步调整观察线程池的活跃线程数、队列长度、任务拒绝率。如果队列经常堆积但 CPU 没跑满说明线程数不够如果线程数很多但 CPU 已经打满说明瓶颈在计算加线程也没用。ThreadPoolExecutor executor new ThreadPoolExecutor( 16, // 核心线程数 32, // 最大线程数 60L, TimeUnit.SECONDS, // 空闲回收时间 new LinkedBlockingQueue(200), // 有界队列防止无限堆积 new ThreadFactory() { // 自定义线程名方便排查 private final AtomicInteger counter new AtomicInteger(0); Override public Thread newThread(Runnable r) { return new Thread(r, biz-async- counter.incrementAndGet()); } }, new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );这里有几个细节值得说。队列一定要有界用LinkedBlockingQueue不传容量的话默认是Integer.MAX_VALUE任务堆积到内存溢出你都不知道。线程名一定要自定义线上排查问题时jstack里看到一堆pool-1-thread-2你根本分不清是哪个业务的。拒绝策略要慎重选CallerRunsPolicy会让提交任务的线程自己执行相当于一种背压能防止任务丢失但会阻塞调用方AbortPolicy直接抛异常适合能接受失败的场景。3.3 线程池隔离的必要性在一个稍微复杂点的系统里不同业务用的线程池应该隔离。比如订单查询的异步任务和推荐服务的异步任务如果共用一个池子推荐服务突然变慢把池子占满订单查询也会跟着挂。这就是所谓的故障传染。我一般会按业务域划分线程池每个池子独立配置参数互不影响。隔离的粒度不用太细太细了线程资源浪费严重。我的经验是按调用特征分组快且频繁的放一组慢且不频繁的放一组关键路径单独一组。这样既能隔离故障又不至于线程数爆炸。4. 任务编排的语义细节比你想的复杂4.1 thenApply、thenCompose、thenCombine 的区别这三个方法名字很像但语义完全不同用错了会导致性能问题或者逻辑错误。thenApply是转换接收上一个任务的结果返回一个新值前后是同步的映射关系。比如future.thenApply(len - len * 2)。它适合做轻量的数据加工。thenCompose是扁平化接收上一个任务的结果返回一个新的CompletableFuture然后把两层CompletableFuture拍平。它适合下一步操作本身也是异步的场景。比如你先查用户 ID再根据 ID 异步查订单这时候必须用thenCompose用thenApply会得到CompletableFutureCompletableFutureOrder这种嵌套结构。thenCombine是合并等两个独立的CompletableFuture都完成后把两个结果一起处理。它适合并行调用两个不相关的服务然后汇总。我见过最常见的错误是用thenApply去调一个返回CompletableFuture的方法结果拿到嵌套结构然后join()两次代码又臭又长。记住一个简单的判断下一步是同步计算用 thenApply下一步是异步调用用 thenCompose两个独立任务要合并用 thenCombine。4.2 thenAccept 和 thenRun 的取舍thenAccept接收结果但不返回值thenRun既不接收结果也不返回值。这两个方法适合在链路末端做收尾操作比如记录日志、发送通知。它们的返回值是CompletableFutureVoid意味着你没法在这个链路后面继续传递数据了。这里有个容易忽略的点thenAccept和thenRun都不会把异常往下传如果前面的任务抛了异常它们会被跳过异常会保留在返回的CompletableFuture里。所以如果你在链路末端用thenRun做清理工作要意识到它可能根本不会执行。4.3 组合多个任务的两种模式当你有多个任务需要编排时CompletableFuture提供了两组静态方法allOf和anyOf。allOf等所有任务完成返回CompletableFutureVoid。注意它不携带结果你需要自己从各个子CompletableFuture里join()取。这个设计有点反直觉但没办法因为各个子任务的结果类型可能不同没法统一返回。anyOf等任意一个任务完成返回CompletableFutureObject结果是第一个完成的任务的值。它适合多个数据源取最快的一个这种场景比如同时查本地缓存和远程服务谁先返回用谁。CompletableFutureString a CompletableFuture.supplyAsync(() - A, executor); CompletableFutureString b CompletableFuture.supplyAsync(() - B, executor); CompletableFutureString c CompletableFuture.supplyAsync(() - C, executor); // 等全部完成 CompletableFutureVoid all CompletableFuture.allOf(a, b, c); all.thenRun(() - { String ra a.join(); String rb b.join(); String rc c.join(); // 汇总处理 }); // 取最快的一个 CompletableFutureObject any CompletableFuture.anyOf(a, b, c);用allOf的时候有个坑如果其中某个任务失败了allOf返回的CompletableFuture会立即以异常完成但其他任务并不会被取消它们还在后台跑。如果你不处理这些孤儿任务会白白消耗资源。所以用allOf时最好配合异常处理或者在失败时主动取消其他任务。5. 异常处理不处理就等于埋雷5.1 异常是怎么在链路里传播的CompletableFuture的异常传播机制和同步代码很不一样。在同步代码里异常会沿着调用栈往上抛你很容易看到。但在异步链路里异常被封装在CompletableFuture内部如果你不主动处理它就会一直潜伏在那里直到你调用join()或get()的时候才爆发出来。更麻烦的是链式调用中如果某一步抛了异常后续的thenApply、thenAccept都会被跳过异常会直接传递到链路的末端。这意味着你中间步骤的清理逻辑可能不会执行。我见过一个案例开发者在链路中间用thenAccept关闭一个资源结果前面步骤抛异常资源没关导致连接泄漏。5.2 exceptionally、handle、whenComplete 的分工CompletableFuture提供了三个处理异常的方法用途各不相同。exceptionally是兜底只在异常发生时执行接收异常返回一个默认值。它适合出错时给个降级结果的场景。CompletableFutureString result CompletableFuture .supplyAsync(() - callRemote(), executor) .exceptionally(ex - { log.warn(远程调用失败返回默认值, ex); return default; });handle是统一处理无论成功还是失败都会执行接收结果和异常两个参数返回一个新值。它适合成功和失败都要走同一套后续逻辑的场景。CompletableFutureString result CompletableFuture .supplyAsync(() - callRemote(), executor) .handle((value, ex) - { if (ex ! null) { return fallback; } return value.toUpperCase(); });whenComplete是旁路观察无论成功失败都会执行但它不改变结果返回的CompletableFuture还是原来的结果或异常。它适合做日志记录、监控埋点这类只观察不干预的操作。CompletableFutureString result CompletableFuture .supplyAsync(() - callRemote(), executor) .whenComplete((value, ex) - { if (ex ! null) { log.error(调用失败, ex); } else { log.info(调用成功结果长度 {}, value.length()); } });选择哪个取决于你要不要改变结果。要降级用exceptionally要统一转换用handle只做记录用whenComplete。5.3 一个容易被忽略的异常吞噬场景有一种情况特别隐蔽你在链路里用了thenApply里面又开了一个新的CompletableFuture但没有返回它那个新任务如果抛异常你是感知不到的。比如future.thenApply(result - { CompletableFuture.runAsync(() - { // 这里的异常会被吞掉 doSomethingRisky(); }, executor); return result; });这个runAsync返回的CompletableFuture被丢弃了它的异常没人处理也不会传播到主链路。这种游离的异步任务是线上问题的常见来源。解决办法是要么把它的CompletableFuture返回出去用thenCompose串起来要么在内部加exceptionally兜底。6. 超时、取消与资源回收6.1 orTimeout 和 completeOnTimeout 的差异Java 9 给CompletableFuture加了两个超时方法解决了长期以来没法给异步任务设超时的痛点。orTimeout在超时后让CompletableFuture以TimeoutException异常完成。completeOnTimeout在超时后用一个默认值完成。前者适合超时就是错误的场景后者适合超时给降级值的场景。// 超时抛异常 CompletableFutureString f1 CompletableFuture .supplyAsync(() - callRemote(), executor) .orTimeout(500, TimeUnit.MILLISECONDS); // 超时给默认值 CompletableFutureString f2 CompletableFuture .supplyAsync(() - callRemote(), executor) .completeOnTimeout(timeout-default, 500, TimeUnit.MILLISECONDS);但这里有个重要的坑这两个方法只是让 CompletableFuture 提前完成并不会真正中断底层任务的执行。也就是说如果callRemote()还在阻塞它会继续跑下去直到自己结束。CompletableFuture只是不再等它了。所以如果你的任务持有资源比如数据库连接超时后资源不会自动释放需要你自己在任务内部处理中断。6.2 cancel 为什么经常不生效CompletableFuture.cancel()的行为也容易让人误解。它会把CompletableFuture标记为已取消但如果任务已经在执行了cancel并不能中断它。只有当任务还没开始执行时cancel才能阻止它启动。这跟Future.cancel(true)不一样后者会尝试中断线程。CompletableFuture没有提供中断能力因为它的设计假设是任务本身应该响应中断。所以如果你需要真正的取消得在任务内部检查中断标志或者用其他机制比如 volatile 标志位来协作式地停止任务。6.3 超时后的资源清理实践在实际项目里我一般会这样处理超时和资源对于每个异步任务在内部用try-finally确保资源释放同时监听中断信号。如果任务超时了虽然CompletableFuture不再等它但任务自己会在下一次检查中断标志时退出然后走finally释放资源。CompletableFutureString task CompletableFuture.supplyAsync(() - { try (Connection conn getConnection()) { // 执行查询内部会响应中断 return query(conn); } catch (Exception e) { throw new CompletionException(e); } }, executor).orTimeout(500, TimeUnit.MILLISECONDS);这样即使超时了连接也会在任务真正结束时被关闭不会泄漏。7. 上下文透传异步链路里的隐形杀手7.1 ThreadLocal 在异步场景下为什么会失效这是异步编程里最容易被忽视的问题之一。同步代码里你用ThreadLocal存用户信息、traceId、租户 ID一路传下去都没问题因为整个请求都在同一个线程上。但一旦用了CompletableFuture任务可能跑在完全不同的线程上ThreadLocal里的值就丢了。我见过一个线上问题某个接口的日志里 traceId 全是空的排查了半天才发现是因为用了异步调用ThreadLocal里的 traceId 没传过去。这种问题不会导致功能错误但会让排查问题变得极其困难。7.2 手动透传的几种方案最简单的方案是手动把需要的上下文作为参数传进任务里。比如String traceId TraceContext.getTraceId(); CompletableFutureString future CompletableFuture.supplyAsync(() - { TraceContext.setTraceId(traceId); try { return doWork(); } finally { TraceContext.clear(); } }, executor);这种方式最直接但代码侵入性强每个异步任务都要写一遍。如果上下文项很多或者链路很长维护起来很痛苦。另一种方案是用InheritableThreadLocal它能让子线程继承父线程的值。但问题是CompletableFuture用的线程池里的线程是复用的不是每次新建所以InheritableThreadLocal只在创建线程时继承一次后续复用不会更新。这个方案在池化场景下基本不可用。7.3 装饰器模式的通用解法比较优雅的方案是用装饰器模式包装Executor在任务提交时捕获当前上下文在执行时恢复。这样业务代码完全不用改只要把线程池包装一下就行。public class ContextAwareExecutor implements Executor { private final Executor delegate; public ContextAwareExecutor(Executor delegate) { this.delegate delegate; } Override public void execute(Runnable command) { MapString, String context ContextHolder.snapshot(); delegate.execute(() - { MapString, String backup ContextHolder.snapshot(); try { ContextHolder.restore(context); command.run(); } finally { ContextHolder.restore(backup); } }); } }这个方案的关键是快照和恢复提交任务时把当前线程的上下文快照下来执行时恢复到工作线程执行完再把工作线程原来的上下文恢复回去因为线程是复用的不能污染。这样既保证了上下文透传又不会串味。8. 性能调优与常见反模式8.1 任务粒度太细反而更慢前面提过CompletableFuture不是性能优化工具。我做过一个测试把一个简单的字符串拼接操作拆成 10 个CompletableFuture并行执行结果比串行慢了将近 3 倍。原因就是任务太细线程调度和上下文切换的开销远超计算本身。判断粒度是否合适的经验法则是如果单个任务的执行时间小于 1ms就不值得异步化。异步化的收益来自并行等待如果任务本身很快并行带来的收益抵不过调度开销。对于 IO 任务因为等待时间长粒度可以细一些对于 CPU 任务粒度要粗最好接近 CPU 核心数。8.2 避免在异步链路里做阻塞操作这是最常见的反模式。在thenApply、thenAccept里做数据库查询、HTTP 调用、Thread.sleep都会阻塞工作线程。如果用的是commonPool直接拖垮全局如果用的是自己的池子也会降低吞吐。正确的做法是阻塞操作要么放在supplyAsync里它本来就是异步执行的要么用thenCompose包一层新的异步任务。比如// 错误在 thenApply 里阻塞 future.thenApply(result - { return jdbcTemplate.queryForObject(...); // 阻塞 }); // 正确用 thenCompose 包成异步 future.thenCompose(result - CompletableFuture.supplyAsync(() - jdbcTemplate.queryForObject(...), dbExecutor) );8.3 链路太长导致的调试困难CompletableFuture的链式调用写起来很爽但链路一长出问题就很难定位。异常堆栈里全是CompletableFuture的内部类根本看不出是哪一步出的错。我的做法是在关键节点加日志和监控用whenComplete记录每一步的耗时和结果状态。另外给每个异步任务起有意义的名字通过自定义ThreadFactory这样在jstack和线程 dump 里能快速定位。future.whenComplete((result, ex) - { if (ex ! null) { log.error(步骤A失败耗时 {}ms, System.currentTimeMillis() - start, ex); } else { log.info(步骤A完成耗时 {}ms, System.currentTimeMillis() - start); } });8.4 一个综合的性能对比表下面这张表是我在实际压测中总结的对比了不同配置下的表现测试环境8 核 CPU任务为模拟 100ms 的 IO 等待并发 1000配置方案平均响应时间吞吐量问题串行调用300ms低无并行延迟叠加commonPool 默认不稳定低阻塞任务占满池子自定义池 16 线程105ms高表现良好自定义池 64 线程102ms高线程过多切换开销略增自定义池 8 线程180ms中线程不足任务排队从表里能看出线程数不是越多越好也不是越少越好要匹配任务的等待特性。16 线程在这个场景下是比较合适的点。9. 我在实际项目里总结的几条铁律聊了这么多原理和细节最后分享几条我在实际项目里用血泪换来的经验都是可以直接落地的。第一条永远不要用默认线程池跑阻塞任务。这是最重要的一条没有例外。每个业务模块配自己的线程池参数根据任务特性调队列必须有界线程名必须自定义。第二条异常处理必须显式写。不要指望异常会自己冒出来异步链路里的异常不处理就是埋雷。每个可能失败的链路末端都要有exceptionally或handle兜底。第三条超时是必须的。任何跨进程的异步调用都要设超时用orTimeout或completeOnTimeout。没有超时的异步调用在依赖方挂掉的时候会把你的线程池拖死。第四条上下文透传要用装饰器统一处理。不要在每个任务里手动 set/get容易漏。包装一个ContextAwareExecutor一劳永逸。第五条链路要可观测。关键节点加日志和耗时统计线程命名要有意义。异步代码的调试成本比同步高得多可观测性是唯一的救命稻草。第六条任务粒度要合理。小于 1ms 的任务不要异步化CPU 密集任务粒度要粗IO 密集任务可以细一些。异步化的收益来自并行等待不是来自看起来更高级。这几条看起来简单但真正做到需要团队形成共识。我见过太多项目代码里到处是CompletableFuture.supplyAsync(() - ...)不带线程池问起来就是能跑就行。等到线上出问题排查成本是预防成本的几十倍。CompletableFuture这个 API 本身不难难的是把它放进一个真实的、有并发、有故障、有监控的工程环境里还能稳定运行。希望这篇文章能帮你少走一些弯路。如果你正在做异步改造建议先从线程池隔离和异常处理这两件事入手这两块做好了大部分坑就避开了。

相关新闻

fpinscala 解析器组合子:用 ParseState 与 isSliced 标志实现高效 slice(练习 17 深度解读)

fpinscala 解析器组合子:用 ParseState 与 isSliced 标志实现高效 slice(练习 17 深度解读)

示例工程 【免费下载链接】fpinscala Code, exercises, answers, and hints to go along with the book "Functional Programming in Scala" 项目地址: https://gitcode.com/gh_mirrors/fp/fpinscala 点击查看 免费下载 导读 本篇文章围绕《Functional …

2026/10/12 13:27:39 阅读更多 →
9款降AI率工具实测:论文初稿如何从78%降到安全过检

9款降AI率工具实测:论文初稿如何从78%降到安全过检

1. 开始之前:这次测评到底解决什么问题 最近后台被问爆了一个问题: “论文初稿用AI辅助写的,学校一检测AI率直接标红,有没有真正好用的降AI率工具?” 说实话,我早在2024年就跟踪过这一波工具,…

2026/10/12 13:27:39 阅读更多 →
Java List接口特点全解析:有序可重复索引访问与实现类选型

Java List接口特点全解析:有序可重复索引访问与实现类选型

很多做Java开发的朋友,不管是校招还是社招,面试时几乎都会被问到一句:“List接口有什么特点?”我当年第一次听到这个问题,心里还犯嘀咕——这有什么好问的,不就是一个集合接口吗?结果面试官接着…

2026/10/12 13:27:39 阅读更多 →

最新新闻

压力测试实战:从阶梯加压到瓶颈定位的完整流程与避坑指南

压力测试实战:从阶梯加压到瓶颈定位的完整流程与避坑指南

把压力测试和性能调优做成一套能落地的流程,而不是零散地“压一压、调一调”,这件事本身比想象中的复杂。很多团队其实不缺工具,缺的是一套完整的思维框架:从目标设定、场景设计、指标观测,到瓶颈定位、调优验证、回归…

2026/10/12 20:43:23 阅读更多 →
Python爬虫框架大比拼:从requests到Selenium,TaoToken统一Key打通采集链路

Python爬虫框架大比拼:从requests到Selenium,TaoToken统一Key打通采集链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 20:43:23 阅读更多 →
策略模式在半导体可靠性测试上位机中的应用实践

策略模式在半导体可靠性测试上位机中的应用实践

我先坦白一个判断:但凡做过半导体测试系统上位机的人,看到“策略模式”四个字,多半会先愣一下——因为这活儿听起来太“业务重、学问深”了,怎么和设计模式扯上关系?但实际上,我恰恰是在一个可靠性测试机上…

2026/10/12 20:43:22 阅读更多 →
校园网流量可视化平台设计:从SNMP采集到时序存储与Web展示

校园网流量可视化平台设计:从SNMP采集到时序存储与Web展示

做校园网运维的人,最怕的就是用户打电话说“网好卡”,但你连卡在哪、是出口拥堵还是某台设备故障、是某个应用占了带宽还是某个区域中了病毒都说不清楚。没有流量可视化的校园网,就像一个没有仪表盘的机房,设备再多也是两眼一抹黑…

2026/10/12 20:43:22 阅读更多 →
Spring Boot超市货物管理系统源码解析:从库存设计到部署实践

Spring Boot超市货物管理系统源码解析:从库存设计到部署实践

最近不少朋友在找Spring Boot练手项目,我翻到一个细节做得比较完整的超市货物管理系统案例,源码可以直接拉下来跑。这个系统覆盖了商品管理、库存管理、供应商管理和销售结算这些核心环节,用的是Spring Boot全家桶加MySQL的经典组合。如果你正…

2026/10/12 20:43:22 阅读更多 →
特殊字符压力测试与性能调优:从字符处理到系统稳定性实战

特殊字符压力测试与性能调优:从字符处理到系统稳定性实战

1. 特殊字符为何隐藏着性能杀手做接口压测和性能调优这么多年,大部分团队的第一反应都是先看数据库慢查询、缓存命中率、线程池配置,很少有人会第一时间想到“特殊字符”。但恰恰是这种不起眼的输入,往往能把一个看似坚挺的系统打到崩溃。所谓…

2026/10/12 20:42:21 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →