Java线程中断与Happens-Before:并发编程可见性规则实战解析

1. 线程中断:协作式取消的正确打开方式

先把结论摆在这里:Java里的线程中断不是“强制终止线程”,而是一个协作式的通知机制。也就是说,A线程想停掉B线程,不能直接动手把B拽下来,而是给B打个招呼——“你该停了”,至于B收到消息之后停不停、什么时候停、怎么优雅地停,那是B自己的事。

很多刚从其他语言转过来的同学最容易踩的坑,就是以为Thread.stop()能像杀进程一样干掉线程。这个方法早就不推荐使用了,强制终止会让线程持有的锁直接释放,monitor处于不一致状态,轻则数据错乱,重则整个应用挂掉。真正规范的做法,就是基于interrupt的协作式中断。

1.1 三兄弟:interrupt、isInterrupted、interrupted

线程中断相关的API就三个,但用法上的细节差异非常容易搞混。

  • thread.interrupt():给目标线程设置中断标志位,相当于发信号。
  • thread.isInterrupted():查询目标线程的中断标志位,不会清除标志。
  • Thread.interrupted():查询当前线程的中断标志位,会清除标志。

第三个别看只是多了个static,语义天差地别。Thread.interrupted()本质上是“获取并重置”操作,常用于循环处理中断信号的场景——你消费了这个中断事件,下次再查就是false了。

另外一个容易忽略的点:如果中断的线程正卡在Object.wait()、Thread.sleep()、Thread.join()这类可中断方法上,调用interrupt()会立刻让这些方法抛出InterruptedException并清除中断标志位。这里就有个经典误区,很多新手在catch里什么都不做,或者只是打一行日志,标志位已经被清掉了,整个中断信号就丢了。

public class InterruptDemo { public static void main(String[] args) throws InterruptedException { Thread worker = new Thread(() -> { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("检测到中断,开始清理资源..."); break; } // 模拟业务处理 try { Thread.sleep(200); } catch (InterruptedException e) { System.out.println("sleep中被中断,重新设置标志位"); Thread.currentThread().interrupt(); } } }); worker.start(); Thread.sleep(1000); worker.interrupt(); } }

注意catch块里那句Thread.currentThread().interrupt(),这一行是能否正确传递中断信号的关键。sleep()抛出异常后会把标志位清掉,你要是不重新设置,外层循环永远看不到中断,线程就“假死”了——它还以为自己还在正常运行,实际上已经无法响应取消请求了。

1.2 中断状态与异常传播

关于InterruptedException有一条不成文的铁律:要么重新设置中断标志,要么直接往外抛,二选一,不能吞掉。

很多框架代码里能看到类似的模式:

public void run() { while (!Thread.currentThread().isInterrupted()) { try { doWork(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 记录日志后退出循环 break; } } }

还有一种处理方式是干脆不catch,让异常沿着调用栈往上传播。比如实现Runnable接口时,run()方法本身不能抛受检异常,但如果是普通的业务方法,直接throws InterruptedException反而更干净。谁调用的谁负责,中断信号不会因为局部catch而消失。

实操心得:我在做某个模拟项目X的时候,需要给一批批量任务加上“可取消”功能。一开始在catch块里只打了日志没重设标志位,结果取消按钮点了两次才有反应,排查了半天才发现是标志位丢失。后来统一规范:所有捕获InterruptedException的地方,要么throw e,要么立即重设标志,再也没出现过类似问题。

1.3 线程池场景下的中断处理

线程池里处理中断,比单纯管理单个线程要复杂一层。ExecutorService提供了两个关闭方法:

  • shutdown():不再接收新任务,已提交的任务继续跑完,内部会检查中断状态,但不会主动interrupt工作线程。
  • shutdownNow():尝试停止所有正在执行的任务,返回尚未开始的任务列表,实现方式是遍历工作线程挨个调用interrupt()。

注意shutdownNow()有个前提——它依赖任务本身对中断的响应能力。如果你的任务在循环里不使用可中断方法,也不检查isInterrupted(),那线程池“立即关闭”就是一句空话。

另外有个容易被忽略的坑:submit()返回的Future对象,它的cancel(true)底层也是调用interrupt()。但如果任务已经用execute()提交,拿不到Future,想取消就只能靠外部维护线程引用了,操作起来很别扭。所以需要支持取消的任务,尽量用submit()并保存Future。

还有一个细节值得说:FutureTask在任务执行完或者异常退出后,调用cancel()是无效的,isCancelled()会返回true但任务已经跑完了。如果你用get()等结果,任务线程被中断了,抛出来的不是InterruptedException而是CancellationException,别搞混了。

2. JMM:Java内存模型的底层逻辑

线程中断解决的是“怎么停”的问题,JMM要解决的是“数据什么时候可见”的问题。

JMM全称Java Memory Model,它的核心目标就两个:一是让并发程序在不同硬件平台上的行为一致,二是给开发者提供一套清晰的内存可见性规则。它定义了一个抽象的内存模型——线程之间不直接共享数据,而是各自有一份工作内存,线程对变量的读写都发生在工作内存中,什么时候同步回主内存,JMM管不着,但它规定了一套规则,告诉你什么时候一定能看到别的线程的修改。

2.1 主内存与工作内存的抽象

用大白话打个比方:主内存是公司的中央数据库,每个线程的工作内存是它自己的本地缓存。线程读数据先读缓存,写数据先写缓存,你啥时候刷新到数据库、啥时候从数据库重新拉取,取决于你用的同步机制。

这就带来三个经典问题:

  1. 可见性:线程A改了变量但没同步回主内存,线程B从主内存读到的就是旧值。
  2. 原子性:一个操作如果被拆成多条指令执行,中间可能被切走。
  3. 有序性:编译器为了性能会做指令重排,代码里的先后顺序未必是实际执行的顺序。

volatile关键字能解决可见性和有序性,但解决不了原子性。i++这种操作,就算i是volatile的,该出并发问题还是出。这个知识点面试常问,实际开发中也特别容易搞混——用volatile去“优化”计数器的并发安全,方向就完全错了。

2.2 volatile的内存语义

volatile变量有两个关键语义:对volatile变量的写操作,会立刻刷新到主内存;对volatile变量的读操作,会从主内存重新读取。这保证了读线程一定能看到最近写入的值。

同时,volatile还有一个“禁止指令重排”的语义。这个特性最典型的应用场景就是单例模式的**双重检查锁(DCL)**写法。

public class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }

这里volatile的作用不是保证可见性——synchronized已经保证可见性了——而是防止指令重排。new Singleton()这行代码在底层要经历三步:分配内存、初始化对象、把引用赋值给instance。如果没有volatile,第二步和第三步可能被重排成“先把引用赋值,再执行初始化”。此时另一个线程进来判断instance != null,拿到的就是一个还没初始化完成的对象,用起来直接空指针。

这个坑非常隐蔽,因为它在低并发下几乎不会出现,一旦流量上去就随机炸。我见过不止一个人说“我写的单例跑了几年都没问题”,那是因为没有触发到重排的临界时机,不代表写法是对的。

2.3 synchronized与锁的内存语义

synchronized的内存语义更重:线程加锁时,会清空工作内存中的共享变量,强制从主内存重新读取;线程释放锁时,会把工作内存中的修改全部刷新到主内存。

换句话说,锁的获取和释放,天然形成了一个内存屏障。同一个锁保护的所有共享变量,在临界区内的读写对后续获取该锁的线程一定可见。这就是为什么用synchronized包起来的读写操作,不需要额外再加volatile的原因。

这两者的选择思路很清晰:只是需要一个“开关状态”让多个线程看到,用volatile足矣;需要保证一组读改写操作的原子性,必须上synchronized或Lock。最怕的就是把两者混用还不理解各自边界,最后排查问题的时候一个变量一个变量地猜,效率极低。

3. happens-before:并发可见性的判定准则

JMM规范里最难啃但也最实用的一块,就是happens-before规则。它回答的问题是:在什么情况下,线程A对变量的写操作,对线程B的读操作是一定可见的?

如果A的写happens-before B的读,那么A写之后的任何操作结果,B都能看到。如果两者之间没有happens-before关系,那JMM不保证可见性——B读到旧值也好,读到新值也好,都是合法行为,程序不能依赖这种“碰运气”。

3.1 八条规则的记忆与理解

JMM定义了八条happens-before规则,我按实用频率排个序:

规则内容实际场景
程序次序规则同一个线程内,按代码顺序,前面的操作happens-before后面的操作单线程代码不需要关心可见性
管程锁规则锁的解锁happens-before后续对同一把锁的加锁synchronized块内变量的可见性
volatile变量规则对volatile变量的写happens-before后续对该变量的读状态标志位、DCL单例
线程启动规则start()调用happens-before该线程内的任何操作新线程一定能看到启动前设置的变量
线程终止规则线程内的所有操作happens-before其他线程检测到该线程终止join()返回后一定能看到线程内的修改
中断规则对线程的interrupt()调用happens-before被中断线程检测到中断中断协作的可见性基础
终结器规则对象构造完成happens-beforefinalize()开始基本很少用了
传递性如果A happens-before B,B happens-before C,则A happens-before C推导可见性链条的核心依据

大部分业务代码里,真正频繁用到的是前四条和传递性。后几条偏理论,但理解了也不亏——比如线程启动规则,你在线程start之前设置了一个普通变量,新线程里读取它,为什么一定能看到?因为start()操作和线程体内代码之间存在happens-before关系。很多人写线程安全的代码时没意识到这一点,凭直觉觉得“应该能看见吧”,其实背后有明确规则支撑。

3.2 用happens-before分析实际场景

我举一个特别典型的例子,模拟某个跨平台系统的任务调度模块。主线程给工作线程传一个配置对象,然后启动线程:

Config config = new Config(); config.setTimeout(5000); config.setRetry(3); Thread worker = new Thread(() -> { // 这里读config,一定能看到上面三行的写入 System.out.println(config.getTimeout()); }); worker.start();

依据“线程启动规则”,主线程在调用start()之前写入config的所有字段,对worker线程而言全部可见。所以这里不用加任何同步手段,也不会有可见性问题。

再看另一个场景,主线程等待worker结束之后读取结果:

Thread worker = new Thread(() -> { result = compute(); // 普通变量 }); worker.start(); worker.join(); // 此处读result,一定能看到compute()的结果

依据“线程终止规则”,如果join()正常返回,说明worker线程已经终止,那么worker内的所有写操作都对主线程可见。这也是为什么join()之后不需要加锁就能安全读取结果的原因。

这两个例子,本质上都是直接用happens-before规则推导出来的结论。反过来,如果没有happens-before关系,比如两个线程之间没有通过锁、volatile、join、start建立任何关联,那读写顺序就是完全不确定的。这时候你以为代码会按某种顺序执行,实际上JMM允许各种奇怪的交错,这就叫数据竞争。

3.3 为什么有了锁还需要happens-before

很多刚接触并发的人会有个疑问:Java已经有了synchronized和volatile,为什么还要单独学习happens-before?

关键在于:语法机制是手段,happens-before是判定标准。你写synchronized的时候,真正在两段代码之间建立“可见性契约”的是管程锁规则这条happens-before关系。搞清楚规则,你才能推理出复杂场景下到底哪些变量可见,哪些不可见,而不是靠猜。

举个例子,两个线程都加锁,但在不同的锁对象上加锁:

synchronized (lockA) { shared = 1; } // 另一个线程 synchronized (lockB) { read(shared); }

这里虽然都用了synchronized,但锁对象不同,管程锁规则不生效,没有happens-before关系,shared的写入对另一个线程没有可见性保证。这种问题用调试工具看表面代码根本看不出毛病,只能通过规则推导才能定位。

再复杂一点的场景,结合传递性推导。比如:

  1. 线程A在synchronized块里写入x = 1,然后给volatileFlag = true。
  2. 线程B读volatileFlag == true,然后进入synchronized块读取x。

依据volatile规则,A对volatileFlag的写happens-before B对volatileFlag的读;依据程序次序规则,A写xhappens-before A写volatileFlag;依据传递性,A写xhappens-before B读x。所以B一定能看到x = 1。

这种多规则组合推导在复杂的并发设计里非常有用。我写并发代码的习惯是,先画出所有共享变量的读写位置,再逐一检查每对读写之间有没有happens-before链。画得出来,就安全;画不出来,就赶紧加锁或者改成不共享。不用等到线上出诡异问题再来背锅。

4. 常见问题与排查技巧实录

并发问题最大的特点是“不常在低并发下复现”,一旦生产环境出问题,单纯靠打日志和断点很难追。我把这几年遇到过的问题整理成速查表,每一条背后都是真实的代码教训。

4.1 高频踩坑清单

表现根本原因解决方案
线程“停不下来”,任务一直跑没有检查中断标志,或catch InterruptedException后丢失状态循环条件里加isInterrupted()检查,catch里重设标志
volatile变量计数不准确volatile不保证原子性,i++被拆成多条指令改用AtomicInteger或synchronized
单例偶发空指针DCL缺少volatile,构造过程被重排给instance加volatile
值一会儿看得见一会儿看不见没有建立happens-before关系,数据竞争用锁、volatile或线程启动/终止规则建立可见性
使用了不同锁导致同步失效加锁的锁对象不一致,管程锁规则不生效确保所有访问共享变量的路径使用同一把锁
shutdownNow()不生效任务不响应中断检查任务代码是否调用了可中断方法或检查标志位

这里面最坑的是最后一条。很多人以为shutdownNow()是“立即杀掉所有任务”,实测下来发现有任务照跑不停,第一反应是自己用错了API。真相对简单——线程池只是负责给工作线程发中断信号,任务代码不配合,中断就是个摆设。

4.2 定位数据竞争的有效手段

排查数据竞争,我常用的思路是三步走。

第一步,明确共享变量清单。把代码里所有跨线程使用的字段列出来,包括对象内部的可变状态、静态变量、缓存容器等。看一遍,哪些是真正只读不变的,哪些是读多写少的,哪些是高频读写的。

第二步,画出读写路径图。对每个共享变量,把所有读和写的位置标出来,然后逐个检查是否满足至少一种同步方式:volatile、synchronized、Lock、或者依赖线程启动/终止规则。

第三步,压测复现。写一个简单的并发测试,开多线程循环读写,尽量模拟生产环境的线程数。如果怀疑某个变量,用JITWatch或者JMH这类工具观察编译后的指令,有时能看到明显的重排痕迹。

我的切身体会是,大部分数据竞争问题在静态分析阶段就能发现,根本不需要等到线上。沉住气把读写路径一条条理清楚,比用任何工具都高效。

4.3 中断与可见性的联动陷阱

还有一个组合场景容易被忽略——中断标志本身也受可见性影响。interrupt()调用和线程内部isInterrupted()检查之间,靠的是“中断规则”建立的happens-before关系。这个规则是规范层面明确保障的,所以标准中断协作代码不需要额外加锁。

但如果你的线程不是通过isInterrupted()检查来响应中断,而是通过一个普通的volatile boolean标志来终止,那中断规则就不覆盖这个场景了。此时就得靠volatile规则来保证标志的可见性,两个机制不能混为一谈。

我遇到过一种写法,用volatile boolean running = true控制循环,catch到InterruptedException后把running设为false。单独看还挺合理,但如果有其他线程通过interrupt()来触发停止,循环退出条件写的是!running,中断标志位压根没用到,线上跑了一段时间发现线程不退出——后来统一改成检查中断状态,这个问题彻底消失。

给一个简单好记的建议:控制线程生命的标志位,尽量用中断机制本身,不要另起炉灶。实在要用自定义标志位,就用volatile,同时一定要保证设置标志和线程检测标志之间存在happens-before关系。

5. 从规则到工具:线程转储与并发复现

聊了这么多理论和规则,最后分享一点做并发排查时的实战工具经验。

线程转储(Thread Dump)是查“线程卡死”类问题的第一利器。当系统表现为某项任务长时间不返回,可以先抓一份线程转储看看线程到底卡在哪一行。如果线程状态是WAITING,大概率是卡在wait()或者park()上;如果是TIMED_WAITING,多半在sleep()或带超时的锁等待;如果是RUNNABLE但一直没有进展,就要再结合CPU占用和业务代码分析了。

复现并发的环境配置也要注意,多核CPU机器配合高并发线程数,更容易暴露可见性和重排问题。我有时会故意把测试线程数调大两倍,把循环次数调大十倍,再用-XX:-TieredCompilation关闭分层编译来增加JIT的编译压力,这样能一定程度提高复现概率。

随手记日志的习惯在并发环境里尤其重要,但日志本身也会改变时序。有一种做法是把每个线程的关键操作记到独立的日志文件,通过时间戳离线对比,比把多线程日志混在一个文件里逐个翻要清晰得多。

6. 最后分享一个习惯

做了这么多年并发编程,我的体会是:并发问题不是靠“小心”解决的,而是靠“规则”解决的。你可以记住所有API的用法,但这不代表你能写出无竞争的代码。真正顶用的,是对happens-before规则的熟练程度——拿到任何一段并发代码,你能快速判断哪些读写是有保障的、哪些是裸奔的,就已经甩开一大半人了。

一个很实用的小技巧:接手一个老项目的并发模块时,先把共享变量和同步机制全部列出来,对照这八条happens-before规则一字一句过一遍。哪怕最后没发现问题,这个过程也能让你对系统的并发安全边界有一个清晰认知。下次线上出问题,你至少知道该往哪个方向排查,而不是瞎猜。

相关新闻

Linux Thermal温控模块详解:从框架设计到设备树配置实战

Linux Thermal温控模块详解:从框架设计到设备树配置实战

1. 项目概述1.1 为什么要啃Linux Thermal模块做嵌入式或者跑服务器的朋友应该都有这种体会:芯片性能越来越强,功耗密度越来越大,散热问题已经从“硬件工程师的烦恼”变成了“系统工程师的噩梦”。我自己手头负责的一块主控板,四核…

2026/10/20 23:14:43 阅读更多 →
N1刷飞牛NAS避坑指南:从U盘启动到救砖恢复完整流程

N1刷飞牛NAS避坑指南:从U盘启动到救砖恢复完整流程

N1刷飞牛NAS这件事,我前前后后帮人折腾过不少次,也在论坛里看过太多翻车案例。很多人拿到教程的第一反应就是下载镜像、拿U盘怼上去开刷,结果要么U盘启动不起来,要么写到一半断电黑屏,最后抱着一个点不亮的盒子到处问怎…

2026/10/20 23:13:40 阅读更多 →
同步FIFO设计实战:空满判断、参数化与避坑指南

同步FIFO设计实战:空满判断、参数化与避坑指南

第一次写同步FIFO时,我遇到过一件让项目停摆两天的事:一个深度为4的小FIFO,连续写满再连续读空之后,空信号和满信号居然同时拉高。一开始我怀疑是仿真库的时序问题,后来翻开RTL逐行排查才发现,是自己对读写…

2026/10/20 23:13:40 阅读更多 →

最新新闻

AF_XDP 实战指南:Ubuntu 24.04 上基于 eBPF 的高性能包处理

AF_XDP 实战指南:Ubuntu 24.04 上基于 eBPF 的高性能包处理

1. 项目概述:AF_XDP 到底解决什么问题碰上 AF_XDP 这个技术点的人,多半是已经在 Linux 上做高性能包处理,或者被 DPDK 的运维成本折腾过之后,想在内核生态里找个折中方案。AF_XDP 不是一个框架,而是 Linux 内核提供的地…

2026/10/20 23:41:33 阅读更多 →
用Unity打造桌面精灵:透明窗口、鼠标穿透与动画实战

用Unity打造桌面精灵:透明窗口、鼠标穿透与动画实战

简介:这是一份基于Unity引擎开发桌面精灵的完整工程资源,面向希望实现类似QQ宠物透明浮动桌面形象的Unity开发者。资源围绕窗口无边界设置、透明材质渲染、UI布局、Animator动画控制以及鼠标交互事件等关键技术点组织,适合有一定Unity基础、想…

2026/10/20 23:41:33 阅读更多 →
GLSL变量与类型系统详解:从标量到矩阵的Shader避坑指南

GLSL变量与类型系统详解:从标量到矩阵的Shader避坑指南

如果你写过一段时间的着色器,大概率遇到过这种时刻:shader编译失败,错误信息指向某个变量,你盯着那一行看了很久,愣是没发现哪里有问题。我印象最深的一次是在某个延迟渲染demo里,片元着色器把法线数据当成…

2026/10/20 23:41:33 阅读更多 →
C语言是什么?从底层原理到上手路径的全面解析

C语言是什么?从底层原理到上手路径的全面解析

编程这行干久了,总会被外行朋友或刚入行的新人问到同一个问题:“C语言到底是什么?” 每次被问到,我都会愣一下,因为这个问题看似简单,真要一两句话说清楚还挺难。它不像Java能“写安卓APP”,也不…

2026/10/20 23:41:33 阅读更多 →
好写作AI如何用游戏化设计破解写作恐惧,养成写作习惯

好写作AI如何用游戏化设计破解写作恐惧,养成写作习惯

1. 好写作AI到底在做什么:把“写作”变成“练级”1.1 它解决了什么问题:写作恐惧不是懒,是“防御”我见过太多人说自己“不会写”“写不出来”,包括我自己也曾是这样。面对一个空白文档,光标一开一合,脑子里…

2026/10/20 23:41:33 阅读更多 →
华三AC WX5560H AP信道优化实战:解决信号满格却卡顿

华三AC WX5560H AP信道优化实战:解决信号满格却卡顿

前阵子在某园区处理无线投诉时,我碰到一个很典型的场景:AP 装了一堆、信号显示也是满格,但视频会议就是动不动卡成 PPT。当时很多人第一反应是带宽不够,可跑到华三 AC WX5560H 上一看,控制器负载很低,出口带…

2026/10/20 23:40:32 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

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