如何避免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
相关产品推荐
相关产品推荐

