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

Java如何实现代码块无中断执行?多线程网络爬虫并发问题求解

问题根因

  1. next()方法存在竞态条件:你对toVisit.size()的判断放在了同步块之外,可能出现判断时队列还有元素,进入同步块前元素被其他线程取走,最终调用get(0)时抛出索引越界异常。
  2. 内部状态泄露:getToVisit()和getVisited()直接返回了内部集合的引用,即使方法加了同步锁,外部拿到引用后仍然可以随意修改集合内容,直接破坏线程安全。
  3. 你提到的"让代码块不被其他线程打断"的需求,只要把所有访问共享可变变量的操作都放在同一个对象锁的synchronized同步块中,就可以保证同一时间只有一个线程执行该代码块,不会出现多线程干扰的问题,你之前的问题是部分共享变量操作漏在了同步块外,并不是同步逻辑本身无效。

修复方案

1. 修复UrlHandler类的线程安全问题

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public class UrlHandler {
    // 移除volatile,引用本身不会变更,用final保证安全
    private final Set<String> visited = new HashSet<>();
    private final List<String> toVisit = new ArrayList<>();

    public void addToVisit(String url) {
        synchronized (this){
            if (!visited.contains(url)) toVisit.add(url);
            // 新增元素后唤醒等待的取数据线程
            this.notifyAll();
        }
    }

    public void addToVisit(Collection<String> urls) {
        synchronized (this){
            for (String url : urls)
                if (!visited.contains(url)) toVisit.add(url);
            this.notifyAll();
        }
    }

    public void addVisited(String url){
        synchronized (this){
            visited.add(url);
        }
    }
    public void addVisited(Collection<String> urls){
        synchronized (this){
            visited.addAll(urls);
        }
    }

    public String next() {
        synchronized (this){
            // 把判空逻辑移入同步块,用wait代替sleep,空等时释放锁不占资源
            while (toVisit.isEmpty()) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e);
                }
            }
            return toVisit.remove(0);
        }
    }

    public String nextRandom() {
        synchronized (this){
            // 补充空队列等待逻辑,和next逻辑对齐
            while (toVisit.isEmpty()) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e);
                }
            }
            int n = 0;
            if (toVisit.size() > 1){
                n = ThreadLocalRandom.current().nextInt(toVisit.size());
            }
            return toVisit.remove(n);
        }
    }

    public List<String> getToVisit() {
        synchronized (this){
            // 返回集合副本,不暴露内部引用
            return new ArrayList<>(toVisit);
        }
    }

    public Set<String> getVisited() {
        synchronized (this){
            // 返回集合副本,不暴露内部引用
            return new HashSet<>(visited);
        }
    }
}

2. 测试用例适配

你现有的测试用例不需要改动,修复后的UrlHandler可以直接通过测试。如果后续有动态添加待爬URL的场景,建议根据实际任务数量提交线程,避免提前结束测试。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 15:18:03