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

如何避免Java中两条鱼线程的重复繁殖逻辑?

问题分析

你的问题本质是多线程并发下的竞态条件:当鱼A和鱼B互相选中对方时,两者的reproduce()方法会同时通过初始判断逻辑,各自触发繁殖流程。你尝试的raddom列表因为没有同步控制,添加/移除操作是非原子的,完全无法阻止并发判断,因此无效。

解决方案

以下提供两种简单高效的解决思路,均能避免重复繁殖问题:

方案一:基于ID排序的单向触发规则

核心思路:规定只有ID更小的鱼才能发起繁殖流程。当A和B互相选中时,只会有ID较小的一方触发繁殖,从根源上避免双向重复执行。同时配合同步锁保护共享资源操作。

修改后的Fish类关键代码

// 新增类级别的静态Random实例,避免频繁创建对象
private static final Random random = new Random();

@Override
public void run() {
    // 补充生命周期终止逻辑,避免线程永久运行
    while (age < lifespan) {
        try {
            Thread.sleep(1000);
            reproduce();
            age++;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            break;
        }
    }
    // 鱼死亡后从鱼缸移除
    synchronized (Aquarium.fishList) {
        Aquarium.fishList.remove(this);
    }
    System.out.println(fishName + " died at age " + age);
}

public void reproduce() {
    Fish randomFish = getRandomFish();
    
    // 先过滤基础不满足的条件
    if (this.age < 4 || randomFish.getAge() < 4 || this.getGender() == randomFish.getGender()) {
        return;
    }

    // 仅允许ID更小的鱼发起繁殖,避免双向触发
    if (this.getId() > randomFish.getId()) {
        return;
    }

    // 同步块保护共享资源操作,防止并发冲突
    synchronized (Aquarium.fishList) {
        // 二次检查条件(防止等待锁期间状态变化)
        if (this.age >= 4 && randomFish.getAge() >= 4 && this.getGender() != randomFish.getGender() && this.getId() < randomFish.getId()) {
            System.out.println(this.fishName + " and " + randomFish.getFishName() + " meet.\n");
            Fish babyFish = createFish();
            Aquarium.fishList.add(babyFish);
        }
    }
}

public Fish getRandomFish() {
    Fish currentFish;
    do {
        int randomNum = random.nextInt(Aquarium.fishList.size());
        currentFish = Aquarium.fishList.get(randomNum);
    } while (this == currentFish);
    return currentFish;
}

配套修改Aquarium类

删除无用的raddom列表,并将fishList改为线程安全集合(或保持原集合但用同步块保护):

public class Aquarium extends Thread {
    // 用同步包装器保证线程安全
    public static List<Fish> fishList = Collections.synchronizedList(new ArrayList<>());

    @Override
    public void run() { }
}

方案二:基于原子操作的配对标记

如果不想依赖ID排序,可以用线程安全的哈希表记录正在配对的鱼对,通过原子操作确保配对只被触发一次。

修改后的Aquarium类

public class Aquarium extends Thread {
    public static List<Fish> fishList = Collections.synchronizedList(new ArrayList<>());
    // 用ConcurrentHashMap记录配对对,键为有序的鱼ID组合(保证A-B和B-A对应同一个键)
    public static ConcurrentHashMap<String, Boolean> matingPairs = new ConcurrentHashMap<>();

    @Override
    public void run() { }
}

修改后的Fish类reproduce方法

public void reproduce() {
    Fish randomFish = getRandomFish();
    
    if (this.age < 4 || randomFish.getAge() < 4 || this.getGender() == randomFish.getGender()) {
        return;
    }

    // 生成有序的配对键,确保双向选中时键唯一
    String pairKey = this.getId() < randomFish.getId() 
        ? this.getId() + "-" + randomFish.getId() 
        : randomFish.getId() + "-" + this.getId();

    // putIfAbsent是原子操作:只有键不存在时才返回null,说明是第一次发起配对
    if (Aquarium.matingPairs.putIfAbsent(pairKey, Boolean.TRUE) == null) {
        try {
            System.out.println(this.fishName + " and " + randomFish.getFishName() + " meet.\n");
            Fish babyFish = createFish();
            Aquarium.fishList.add(babyFish);
        } finally {
            // 完成繁殖后移除标记,允许后续再次配对
            Aquarium.matingPairs.remove(pairKey);
        }
    }
}
额外注意事项
  • 原代码中的ArrayList不是线程安全集合,多个线程同时执行add/remove会引发并发异常,必须替换为线程安全集合或用同步块保护。
  • getRandomFish()方法中频繁创建Random对象会降低性能,建议改为类级别的静态实例。
  • 原run()方法的while(true)没有终止逻辑,会导致线程永久运行,建议补充基于生命周期的终止判断。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 12:15:58