FutureTask 源码深度剖析:Runnable/Future双身份适配与状态机设计
写 Java 并发的时候我最开始对 FutureTask 的认知停留在把 Callable 丢进去然后 get() 拿返回值这个层面。直到有一天我想自己实现一个简单的异步任务才发现 Runnable 的 run() 没有返回值、Callable 能返回但没法直接丢给 Thread两个接口之间横着一条天然的鸿沟。FutureTask 最巧妙的地方就是它同时实现了 Runnable 和 Future 两套身份对线程池来说它是一个普普通通的 Runnable对调用方来说它又是一个可以查询任务状态、阻塞获取结果、取消任务的 Future 凭证。这篇文章我从源码层面拆一下 FutureTask 的适配思路、状态机设计、阻塞唤醒机制和完整执行链路适合那些想深入 JUC 底层、或者已经在用 FutureTask 但始终没搞懂结果到底怎么回来的这类问题的开发者。1. 一个既是任务、又是凭证的类适配的入口设计1.1 Runnable 和 Callable 之间的那条鸿沟先看两个接口本身。Runnable 的定义极简public interface Runnable { void run(); }。run() 没有返回值也不能抛出受检异常它是为线程要执行的一段代码而生的。Callable 则是public interface CallableV { V call() throws Exception; }call() 可以返回一个泛型结果也可以把异常往上抛。这两个接口简直是对着设计的一个能当任务但不能给结果一个能给结果但没法直接交给 Thread 或者 ThreadPoolExecutor 去执行。那早期没有 FutureTask 的时候怎么办我见过不少土办法定义一个共享容器任务线程把结果写进去主线程轮询标志位或者自己维护一个MapRunnable, Object任务执行完往里塞结果主线程按 Runnable 查。这些方案都能跑但线程安全、内存可见性、等待通知全都得自己手搓稍不留神就是并发 Bug。1.2 RunnableFuture把两个身份焊在一起FutureTask 的核心设计可以从它的继承关系一眼看穿public class FutureTaskV implements RunnableFutureV public interface RunnableFutureV extends Runnable, FutureV { void run(); }RunnableFuture 这个接口非常直白你既是 Runnable可以被提交给任何只认 Runnable 的执行器你又是 Future可以调用 get()、cancel()、isDone() 来管理任务生命周期。FutureTask 就是这个接口的默认实现。这种一个对象承担两种职责的设计本质上就是适配器模式的一种落地。调用方视角下提交任务时只需要new FutureTask(callable)然后把它交给线程池或者new Thread(task).start()完全不关心内部适配细节另一个视角下同一个对象保存了任务执行结果后续随时可以通过 get() 把这个结果取出来。1.3 为什么适配不入线程池内部有人可能会问线程池的 execute() 方法只接受 Runnable那为什么不让 ThreadPoolExecutor 内部把 Callable 包一下、再单独维护一个任务到 Future 的映射就完事了呢这个方案理论上可行但代价实在太大。如果在线程池内部做适配就得为每一个提交的 Callable 额外创建包装对象还要用一个并发 Map 保存 Runnable 和 Future 的对应关系。任务完成前 Future 不能丢任务完成后又要清理映射这个映射表的生命周期和线程池绑定在一起内存回收、并发访问全是麻烦。FutureTask 把任务本身和任务凭证合二为一任务对象自己保管结果线程池最多只把 callable 字段置空帮助 GC完全不需要外部映射。这也是 Doug Lea 那一套并发设计里很典型的思路能用对象自身状态解决的问题就不引入额外结构。2. 状态机跨线程安全的最大底座2.1 七个状态分别代表什么FutureTask 内部有一个private volatile int state;字段整个类的状态流转全靠它。状态常量定义如下private static final int NEW 0; private static final int COMPLETING 1; private static final int NORMAL 2; private static final int EXCEPTIONAL 3; private static final int CANCELLED 4; private static final int INTERRUPTING 5; private static final int INTERRUPTED 6;下面是每个状态的含义和最终流向状态数值含义是否终态NEW0任务新建尚未被执行否COMPLETING1正在设置结果outcome 即将写入否NORMAL2正常执行完成是EXCEPTIONAL3执行过程抛出异常是CANCELLED4被取消未中断运行线程是INTERRUPTING5调用 cancel(true)正在中断线程否INTERRUPTED6中断完成是这里有一个看起来很不起眼、实则很关键的点state 只能从 NEW 开始单向推进最终停在 NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED 这四个终态之一。任务一旦完成或者被取消就永远不可能回到 NEW所以 FutureTask 不能复跑。这个设计直接决定了整个类的行为run() 只有在 state 还是 NEW 的时候才真正执行任务其余任何状态下调用 run() 都是直接返回。2.2 为什么强依赖 volatile 和 CASstate 是 volatile 的保证了所有线程读取到这个字段时都能拿到最新写入值状态的变更大量依赖 CAS保证多个线程同时尝试推进状态时只有一个能成功。为什么要这么较真因为 FutureTask 的工作线程、等待结果的线程、发出 cancel 的线程很可能是三个不同的线程它们之间没有任何锁所有的协同都落在 state 之上。举一个最简单的例子任务执行完毕工作线程把 state 从 COMPLETING 推进到 NORMAL主线程在 get() 里循环读 state一旦读到 NORMAL 就认为结果就绪了。如果没有 volatile主线程可能一直读到旧值如果没有 CAS两个线程同时调用 set() 可能会把同一个任务设置两次结果状态就乱了。2.3 outcome 的写入顺序为什么先写结果、后置终态FutureTask 里保存结果的字段是private Object outcome;它本身并不是 volatile 的。你可能会疑惑outcome 不是 volatile那等待线程怎么保证能看到最新结果答案藏在写入顺序里。在 set() 方法中源码大致是这样处理的protected void set(V v) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome v; UNSAFE.putOrderedInt(this, stateOffset, NORMAL); finishCompletion(); } }也就是先把 state 从 NEW CAS 到 COMPLETING然后写入 outcome最后再写 state 到 NORMAL。这是一个典型的发布安全模式普通写操作 outcomev 后面紧跟一个有序写操作 stateNORMAL读到 stateNORMAL 的线程由于设置了 happens-before 关系再回头读 outcome 一定能看到最新值。这就是 COMPLETING 中间态的意义——它创造了一个结果正在落盘的窗口等待线程看到 COMPLETING 时不会把半成品当结果拿走而是乖乖继续等待。3. get() 阻塞的秘密等待链表与 LockSupport 的配合3.1 get() 的快速通道get() 方法源码很短public V get() throws InterruptedException, ExecutionException { int s state; if (s COMPLETING) s awaitDone(false, 0L); return report(s); }首先读一次 state如果任务已经完成state COMPLETING直接进入 report() 把 outcome 取出来如果任务还没完成或者正在完成就进入 awaitDone() 阻塞等待。report() 的逻辑也很清晰NORMAL 就直接强转返回值CANCELLED 或更高状态抛 CancellationExceptionEXCEPTIONAL 就包一层 ExecutionException 抛给调用方。3.2 awaitDone 里的等待链表awaitDone() 是整个 get() 的精髓。FutureTask 内部维护着一个volatile WaitNode waiters的无锁链表每个调用 get() 而被阻塞的线程都会被包装成一个 WaitNode 节点通过 CAS 头插法挂到链上。核心循环逻辑可以简化成下面几步读 state如果大于 COMPLETING说明任务已经完成把自己从链表里摘掉返回状态如果 state 等于 COMPLETING说明结果正在写入当前线程执行Thread.yield()让出一下 CPU等下轮循环再读如果当前线程被中断清理链表中自己的节点抛出 InterruptedException如果节点还没创建创建一个 WaitNode如果节点还没入队用 CAS 把节点头插到 waiters 链表最后调用LockSupport.park(this)或者带超时的LockSupport.parkNanos(this, nanos)真正阻塞。这个链表的目的是什么因为调用 get() 等待同一个任务的线程可能非常多如果用一个锁加条件队列每次唤醒所有线程都免不了锁竞争。用无锁链表加 LockSupport 的好处是等待线程各 sleep 各的任务完成时统一 unpark 就好不需要持有任何锁。3.3 完成后的唤醒finishCompletion任务完成时set() 或 setException() 末尾会调用 finishCompletion()。它的逻辑是先通过 CAS 把 waiters 置为 null拿到旧链表然后遍历链表中每个 WaitNode把节点里的 thread 取出来执行LockSupport.unpark(t)。这样所有正在 get() 里 park 的线程都会被唤醒重新进入循环读取 state发现已经完成就退出等待返回结果。这里必须说一下 LockSupport 相比 Object.wait/notify 的巨大优势。wait/notify 必须在 synchronized 块内使用而且 notify 如果早于 wait 发生线程可能永远睡死过去。LockSupport 的 unpark 可以先于 park 调用每个线程有一个许可permit的概念预先 unpark 一次线程后续 park 时不会真的阻塞直接消费掉这个许可继续执行。因此 FutureTask 的唤醒机制天然不存在过早通知导致丢失信号的问题。3.4 超时版 get 的实现思路带超时版本的get(long timeout, TimeUnit unit)走的是同一个 awaitDone只是 timed 参数为 true。在进入等待之前它会计算一个 deadlineSystem.nanoTime() unit.toNanos(timeout)。每一次循环都重新计算剩余时间nanos deadline - System.nanoTime()如果剩余时间已经小于等于 0就清理掉自己的 WaitNode返回当前 state。外层看到 state 仍然 COMPLETING就抛 TimeoutException如果恰好任务刚刚完成则不抛超时直接返回结果。所以超时语义是到点还没完成就放弃等待并不是严格到纳秒级别剩余一点点时间时任务完成也会正常返回。这里还有一个细节值得注意awaitDone 中会先把 COMPLETING 状态的线程执行 yield 再重读为什么不直接 park因为 COMPLETING 是一个极短的过渡态紧接着状态就会变成 NORMAL 或 EXCEPTIONAL线程让出 CPU 转一圈再读大概率就已经完成省掉一次 park/unpark 的开销。这种对短暂中间态做忙等待的处理方式其实也值得在业务代码里借鉴。4. run() 方法里的每一步从 call() 到结果落袋4.1 第一道防线runner 的 CAS 抢占run() 方法的开头是 FutureTask 整个并发逻辑里我最喜欢的一段public void run() { if (state ! NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; // ... }这里做了两件事先检查 state 是否为 NEW不是就返回然后用 CAS 把 runner 字段从 null 替换成当前线程。runner 字段是 volatile 的这个 CAS 保证了同一个 FutureTask 即使被多个线程同时执行 run()也只有一个线程能够抢占成功真正去调用内部的 Callable其余线程直接返回。你可能会想为什么要多此一举用 runner 来抢占而不是只看 state因为从状态还是 NEW到真正开始调 call()之间有一个时间窗口。如果两个线程同时发现 state 是 NEW 都想执行单靠状态判断不够runner CAS 相当于给谁是这个任务的执行者做了唯一标记。这一手非常巧妙地避免了重复执行而且对线程池的场景也很友好因为同一个任务可能被同一个 Worker 多次 getTask 取到或者被多个 Worker 抢到但真正执行的只有一次。4.2 call() 的结果与异常分别怎么存成功执行时走 set()前面已经讲过先 CAS 到 COMPLETING写入 outcome再推进到 NORMAL。异常执行时走 setException()protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome t; UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); finishCompletion(); } }异常对象本身也会被当作结果存进 outcome只是状态置为 EXCEPTIONAL。这样设计非常克制执行线程的任务就是执行 Callable至于结果如何转交给调用方是 FutureTask 的事异常也一样被包装成一个结果等工作线程把状态推进完成后调用方在 get() 里才把它转换成 ExecutionException 抛出来。为什么要包一层 ExecutionException 而不是直接把原始异常抛给 get()因为 get() 是通用接口无法预知调用方具体想捕获什么类型的异常统一包成 ExecutionException调用方只需要catch (ExecutionException e)再通过e.getCause()拿原始异常整个 API 的使用就非常规整。另外这种设计还避免了受检异常在跨线程传递时被方法签名限制的问题。4.3 finally 里的清理和中断协作run() 的 finally 块里有两段逻辑finally { runner null; int s state; if (s INTERRUPTING) handlePossibleCancellationInterrupt(s); }runner 置 null 是把当前执行者标记清掉方便后续 cancel(true) 等逻辑判断。但这里要注意任务完成之后 state 已经是终态runner 置 null 并不会让任务可以再次执行。handlePossibleCancellationInterrupt 是 JUC 源码里很少见的主动让出等待private void handlePossibleCancellationInterrupt(int s) { if (s INTERRUPTING) { while (state INTERRUPTING) Thread.yield(); } }如果 run() 结束前发现状态是 INTERRUPTING说明有另一个线程正在执行 cancel(true) 并准备 interrupt 当前任务线程。这里必须一直 yield 等到 cancel 线程把状态推进到 INTERRUPTED 再退出否则工作线程可能已经离开 run()而 cancel 线程还在尝试 interrupt导致中断信号丢失。这种处理方式看着笨拙但恰恰是为了保证 cancel 的语义完整。4.4 顺便说说 runAndResetFutureTask 还留了一个受保护的 runAndReset() 方法它和 run() 很像区别是执行完 Callable 后不保存结果、不推进状态到终态而是保持 NEW供 ScheduledThreadPoolExecutor 这类需要周期性执行的任务使用。这个方法平时几乎用不到但理解了它你会更清楚 FutureTask 的状态机设计是多么克制连作为周期任务基石的方法也没破坏任务只能被正确推进一次的核心原则。5. 从 submit 到 get 的完整链路线程池视角5.1 submit 里发生的其实只有两件事平时我们写ExecutorService pool Executors.newFixedThreadPool(4); FutureString f pool.submit(() - hello);的时候AbstractExecutorService 的 submit(Callable) 内部逻辑非常简单public T FutureT submit(CallableT task) { if (task null) throw new NullPointerException(); RunnableFutureT ftask newTaskFor(task); execute(ftask); return ftask; }newTaskFor 默认就是new FutureTask(callable)所以返回的 Future 真实类型就是 FutureTask。这里还有一个模板方法设计newTaskFor 是 protected 的子类可以通过重写它返回自定义的 RunnableFuture很多高性能框架就是这么扩展 FutureTask 的。再往下的 execute(ftask) 就把 FutureTask 当成一个普通 Runnable 交给了线程池。ThreadPoolExecutor 内部完全不知道这个 Runnable 内部藏着一个 Callable也不需要知道——它只负责在某个 Worker 线程里执行 task.run()。5.2 Worker 线程里的旅程把时序捋一下整个执行链路是这样的主线程调用 submit构造出一个 FutureTask通过 execute() 把它作为任务对象加入线程池线程池内部某个 Worker 线程通过 getTask() 拿到这个 taskWorker 线程执行 task.run()也就是 FutureTask 的 run()run() 里通过 runner 的 CAS 抢占到执行权后调用内部包装的 Callable.call()Callable 正常返回结果写入 outcome状态推进到 NORMALfinishCompletion() 唤醒所有等待者主线程在 get() 的 awaitDone() 中被 unpark 唤醒重新读 state 发现已经完成调用 report() 把 outcome 强转成 V 返回。整个过程里线程池只扮演了调度器的角色任务状态管理、结果保存、等待唤醒这些核心逻辑全部由 FutureTask 自己完成。这也正是我能放心把它当作标准并发原语使用的原因——边界划分非常清晰。5.3 cancel 与 get 超时的组合拳实际业务里最常见的用法是配合超时FutureString f pool.submit(callableTask); try { String result f.get(3, TimeUnit.SECONDS); } catch (TimeoutException e) { f.cancel(true); }cancel(true) 的执行流程值得特别注意。它首先判断 state 是否为 NEW再用 CAS 把 state 置为 INTERRUPTING然后取出 runner 字段对应的线程执行 interrupt()最后把状态推进到 INTERRUPTED。也就是说cancel(true) 并不保证任务一定停下来——如果任务内部不响应中断信号它照样会跑完。但从 FutureTask 的角度看它的语义是完整的取消成功后所有在 get() 里等待的线程会被唤醒并且 get() 会抛出 CancellationException。如果任务已经完成再调用 cancelCAS 会失败返回 false。这个细节在业务里挺实用可以用cancel(false)去试探取消一个可能完成了的任务返回 false 就说明任务已经结束不用再担心后台资源泄漏。5.4 不用线程池FutureTask 也能直接用有些轻量场景根本不需要引入线程池FutureTask 配合 Thread 就能工作FutureTaskInteger task new FutureTask(() - { Thread.sleep(1000); return 42; }); new Thread(task).start(); Integer result task.get();Thread 构造器只认 RunnableFutureTask 恰好就是 Runnable。这种写法的价值在于它帮你把异步执行和等待结果两件事统一到了一个对象上代码可读性远好于自己搞一个共享变量再轮询。6. 实战建议与几个容易踩的坑6.1 用 FutureTask 子类重写 done() 做完成回调FutureTask 有一个钩子方法 done()默认实现是空的它会在任务完成、所有等待线程被唤醒之后被调用调用者是完成任务的那个 Worker 线程。利用它可以做一些轻量的完成回调比如打日志、发信号、清理资源FutureTaskString task new FutureTask(() - { Thread.sleep(2000); return done; }) { Override protected void done() { // 这里不需要担心锁任务已完成 System.out.println(run 已完成等待线程正在被唤醒); } };要注意的是done() 里调用 get() 时如果任务是异常完成的会抛出 ExecutionException所以最好用 try/catch 包一层。我第一次用这个钩子的时候就忽略了这一点异常任务在回调里还炸了一次。6.2 几个必须避开的坑不要在 Callable 内部调用同一个 FutureTask 的 get()。这是一个典型的自等死锁执行线程在执行 call()却跑去 get() 等待自己完成而能完成任务的线程就是它自己结果谁也无法推进状态线程永久阻塞。正确做法是只由外部线程调用 get()。不要以为同一个 FutureTask 提交两次会被执行两次。即使在两个线程里执行同一个 task.run()runner 的 CAS 也只能让一个线程成功另一个直接返回。你需要的是每次新 new 一个 FutureTask。使用 get() 无限等待时要格外小心。如果任务因为某种原因始终不结束所有调用 get() 的线程都会一直 park这在线程池场景下可能会把池子里的线程全部占满。一个现实的做法是尽量用带超时的 get(long, TimeUnit)并在超时后评估是否需要 cancel。6.3 和 CompletableFuture 的边界被问得比较多的一个问题是有了 FutureTask 还要 CompletableFuture 干什么我的理解是FutureTask 更接近单个异步任务的结果获取原语它的模型是一个任务一个 Future一次等待CompletableFuture 则是异步编排框架善于把多个异步操作串成流水线还有 thenApply、thenCombine、exceptionally 这些组合操作。如果你只是想让某个后台线程执行一段耗时逻辑并回传结果FutureTask 足够了简单直接语义清晰。如果要做多任务并行编排、回调链、异常恢复那就直接上 CompletableFuture没必要拿 FutureTask 硬凑。两者不是替代关系而是不同抽象层级的产物。6.4 读源码时抓的三根主线最后给想读 FutureTask 源码的朋友一个建议不要一头扎进 CAS 偏移量的细节里抓住三根主线就能把整份源码吃透。第一根线是状态机state 的七种状态如何单向流转哪些是终态哪些是过渡态。第二根线是发布安全outcome 普通字段为什么能安全跨线程读取关键是先写结果、再置终态、等待者读终态后再读结果的顺序保证。第三根线是无锁等待WaitNode 链表加 LockSupport.park/unpark 替代了传统的 synchronized 加 wait/notify解除了锁依赖也消除了唤醒信号丢失的问题。这三根线搞明白之后再看 run()、get()、cancel() 这些方法会发现它们都只是在状态机上做文章而已。最后说点个人体会我第一次追 FutureTask 源码时最惊讶的是整个类几乎没有用到 synchronized 关键字复杂的状态流转全靠 volatile 加 CAS 加 LockSupport 撑起来。后来我写业务代码也养成了一个习惯遇到两个线程要协同一个等结果一个出结果的场景第一反应不是加锁而是先画一个最小状态机把谁负责推进状态、谁负责消费状态理清楚。这种并发思维比死记几个 API 有用得多至少能让你面对各种并发问题时知道往哪个方向去拆解。希望这篇剖析对你也有同样的启发。

相关新闻

【计算机毕业设计单片机案例】基于单片机的室内环境安全指标采集、屏幕闪烁告警与通风装置设计 基于单片机的物联网室内光照空气质量综合监测与智能调控系统设计(030112)

【计算机毕业设计单片机案例】基于单片机的室内环境安全指标采集、屏幕闪烁告警与通风装置设计 基于单片机的物联网室内光照空气质量综合监测与智能调控系统设计(030112)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

2026/10/10 5:37:37 阅读更多 →
MATLAB小波变换雷达信号时频分析实战:原理、代码与参数避坑

MATLAB小波变换雷达信号时频分析实战:原理、代码与参数避坑

1. 从一张"平均过的频谱"说起:雷达信号为什么非得换种看法接手雷达信号时频分析的头一个月,我曾在频谱分析仪前坐了整整半天:示波器上明明是两段完全不同的脉冲——一段频率从低频往高频扫,另一段干脆在高频和低频之间来…

2026/10/10 5:37:37 阅读更多 →
蔬菜定价与补货优化:从数据清洗到数学建模全流程解析

蔬菜定价与补货优化:从数据清洗到数学建模全流程解析

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

2026/10/10 5:37:37 阅读更多 →

最新新闻

LeetCode 2413 Smallest Even Multiple 题解:奇偶分类与位运算的 O(1) 解法(codeforces-go 仓库实战指南)

LeetCode 2413 Smallest Even Multiple 题解:奇偶分类与位运算的 O(1) 解法(codeforces-go 仓库实战指南)

科学计算 【免费下载链接】codeforces-go 算法竞赛模板库 by 灵茶山艾府 💭💡🎈 项目地址: https://gitcode.com/GitHub_Trending/co/codeforces-go 点击查看 免费下载 本篇技术指南以 codeforces-go 仓库中 LeetCode 第 311 场周…

2026/10/10 6:06:48 阅读更多 →
纯虚函数与抽象类:接口该怎么设计

纯虚函数与抽象类:接口该怎么设计

设计一套可扩展的 C 系统时,你迟早会写出「只定义契约、不提供实现」的类:它规定「凡是我的子类都必须有 area() 和 draw(),但怎么实现我不管」。这种类就是抽象类(abstract class),靠 纯虚函数&#xff08…

2026/10/10 6:06:48 阅读更多 →
业务参与者规则不显示构件包?沿工作流运行时加载链路排查

业务参与者规则不显示构件包?沿工作流运行时加载链路排查

看到“业务参与者规则没有显示构件包及构件包下流程事件,work目录下也未生成当前构件包目录”这个描述,我第一反应是:这不是一个单纯的界面显示问题,而是服务在运行态压根没有把目标构件包加载进来。业务参与者规则界面显示的构件…

2026/10/10 6:06:48 阅读更多 →
Linux 端口不通怎么排查?firewalld 不是超时而是 No route to host,三台真机实测 firewalld / ufw / iptables / SELinux

Linux 端口不通怎么排查?firewalld 不是超时而是 No route to host,三台真机实测 firewalld / ufw / iptables / SELinux

这一篇讲什么 「端口不通」是最常见也最容易瞎折腾的问题:服务起了、防火墙也开了,外面还是连不上。本篇在三台机器上把排查链条从头到尾实跑一遍:先分清报错类型 → 本机在不在监听 → 防火墙 → SELinux / AppArmor。 实测环境同(一):CentOS 7.9(VMware)、Rocky 9.8、Ubuntu …

2026/10/10 6:06:48 阅读更多 →
CentOS 7 yum 源失效怎么办?vault 返回 403、EPEL 7 卡死,三台真机实测能用的换源方法(附 Rocky 9 / Ubuntu 24.04)

CentOS 7 yum 源失效怎么办?vault 返回 403、EPEL 7 卡死,三台真机实测能用的换源方法(附 Rocky 9 / Ubuntu 24.04)

这个系列是什么 「Linux 实战」按线上真正会撞上的事情排:认清系统和装软件(本篇)、端口不通、服务起不来、磁盘满了、网络和 SSH、日志与性能…… 每一篇都做两件事:①所有命令在真机上跑过、贴原始输出;②同一件事在三个系统上各跑一遍 —— CentOS 7 还大量在线上跑着,新机器…

2026/10/10 6:06:48 阅读更多 →
极点五笔10周年版:确定性优先的五笔输入法重构

极点五笔10周年版:确定性优先的五笔输入法重构

1. 项目概述:这不只是一个输入法,而是一次对中文输入底层逻辑的重新校准“极点五笔:10周年版全面升级体验”——看到这个标题,我第一反应不是点开下载,而是下意识摸了摸键盘右下角那块被手指磨得发亮的空格键。十年&am…

2026/10/10 6:05:48 阅读更多 →

日新闻

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

卫星轨道分类全解析:从LEO到GEO的选型逻辑与工程实践

1. 从“卫星轨道分类”这个标题说起:为什么值得花时间搞懂第一次接触“卫星轨道分类”这个概念,很多人会觉得它离自己很远——不就是天上的星星怎么转吗?但如果你正在做航天任务规划、遥感数据接收、星座设计,甚至只是准备一场航天…

2026/10/10 0:00:39 阅读更多 →
Spring AOP 核心原理与实战:从概念到日志切面落地

Spring AOP 核心原理与实战:从概念到日志切面落地

1. 从一个真实痛点说起:为什么你的代码里到处都是重复逻辑刚入行那会儿,我写过一个用户管理模块,注册、登录、改密码、注销四个接口。每个接口里都塞了几乎一样的日志打印、参数校验、事务开启和提交。当时觉得没什么,能跑就行。直…

2026/10/10 0:00:40 阅读更多 →
Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

Python招聘数据采集与分析可视化:从采集清洗到薪资技能城市可视化全链路

简介:这是一套面向计算机相关专业学生与项目实战学习者的Python数据采集与分析可视化完整项目,以Boss直聘岗位数据为对象,适合用作毕业设计、课程设计或期末大作业。资源包共38个文件,约246KB,以13个py源码文件为核心&…

2026/10/10 0:00:40 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/10 1:36:08 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/10 5:23:50 阅读更多 →
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/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →