You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

《Java并发编程实战》7.2.5节TrackingExecutor竞态条件时序问题咨询

竞态条件触发时序说明

首先明确两个参与流程的线程:

  • 工作线程:线程池中负责执行CrawlTask的线程
  • 控制线程:调用WebCrawler.stop()方法触发线程池关闭的线程

触发误报的时序步骤

按时间先后顺序的交错执行流程如下:

  1. 工作线程执行runnable.run()(即CrawlTask.run())的所有业务逻辑:爬取页面、解析所有子链接并提交新任务,全部代码执行完成,即将进入finally代码块。
  2. 控制线程在这个极小的时间间隙内发起关闭流程:
    • 首先调用exec.shutdownNow():将线程池状态标记为已关闭,向所有正在运行的工作线程发送中断信号,返回所有未开始执行的排队任务。
    • 随后调用exec.awaitTermination(...),阻塞等待所有工作线程执行结束。
  3. 工作线程恢复执行finally块内的判断逻辑:
    • 第一个判断isShutdown():线程池已被标记为关闭,返回true
    • 第二个判断Thread.currentThread().isInterrupted():工作线程已经收到shutdownNow()发送的中断信号,中断标记为true,返回true
    • 两个条件都满足,工作线程执行tasksCancelledAtShutdown.add(runnable),将已经执行完成的任务误加入取消任务集合。
  4. 工作线程执行结束,线程池标记为完全终止,awaitTermination返回true。
  5. 控制线程调用exec.getCancelledTasks()拿到被误加的已完成任务,调用saveUncrawled将对应URL重新存入待爬取集合,产生误报。

为什么该竞态无法避免

任务最后一条指令执行完成到线程池标记任务为已完成的间隙,可能发生线程池关闭操作。

runnable.run()执行完成后,到finally块的判断逻辑执行前,没有任何同步机制阻止线程池发起关闭和中断操作。这个时间间隙是客观存在的,无法通过加锁等方式完全消除。


配套的兼容设计

WebCrawler中做了幂等处理:seen是线程安全的ConcurrentHashMap,下次误存的URL被提交时,alreadyCrawled()调用putIfAbsent会发现URL已经被爬取过,直接跳过执行,不会产生重复爬取的实际问题。

内容的提问来源于stack exchange,提问作者bimjhi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 20:45:04