【问题标题】:Adding and removing elements to a Collection向集合添加和删除元素
【发布时间】:2015-08-13 22:13:36
【问题描述】:

我是 Java 新手,我目前正在制作这个玩家必须吃一些饼干的游戏。这些 cookie 是 ArrayList 的元素。此 ArrayList 由两个线程修改: - 使用 Iterator.remove() 对其进行迭代并删除已吃掉的 cookie - 每 5 秒向 ArrayList 添加一个 cookie

有时我得到一个 ConcurrentModificationException,我知道这是因为 Iterator.remove() 的行为“如果在迭代过程中以任何其他方式修改基础集合,则未指定”,如 Java 教程中所述太阳。我应该怎么做?

编辑:更新代码

List<Cupcake> cake = Collections.synchronizedList(new ArrayList<Cupcake>());

这是生成器:

public class CupcakeSpawner extends Thread {
    private Background back;

    public CupcakeSpawner(Background back) {
        this.back = back;
    }

    public void run() {
        while(true) {
            if(back.getCake().size() < 15)
                back.getCake().add(new Cupcake());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

我的更新方法:

public void update() {
    List<Cupcake> cake = back.getCake();
    Iterator<Cupcake> itrC = cake.iterator();
    while(itrC.hasNext()) {
        Cupcake cupcake = (Cupcake)(itrC.next());
        checkCollisionCup(cupcake);
        if(cupcake.isEaten()) 
                itrC.remove();
        }
    }
}

【问题讨论】:

  • 你不需要使用 Iterator#next 进行投射:Cupcake cupcake = itrC.next();
  • 另外我不确定你是否需要另一个线程来处理 15 个蛋糕。

标签: java arraylist concurrentmodification


【解决方案1】:

ArrayList 不是 List 的线程安全实现。

改用CopyOnWriteArrayList - 如果您在迭代时添加元素,它不会导致爆炸。

【讨论】:

  • 我认为在问题的上下文中使用 CopOnWriteArrayList 可以显示已经吃过的蛋糕
【解决方案2】:

正如@Bohemian 所说,ArrayList 不是线程安全的。

最好用:

List list = Collections.synchronizedList(new ArrayList());

那你想打电话给Iterator.remove()

synchronized (list) {
  Iterator.remove() 
}

希望这会对你有所帮助。

See this

public class CupcakeSpawner extends Thread {
private Background back;

public CupcakeSpawner(Background back) {
    this.back = back;
}

public void run() {
    List cakes = back.getCake();
    while(true) {
        if(cakes.size() < 15){
            synchronized (cakes) {
                 cakes.add(new Cupcake());
            }
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
      }
   }
}

【讨论】:

  • 如果您将调用嵌入同步块中,使用 Collections#synchronizedList 的原因是什么?
  • 我尝试使用您的建议更新我的代码。对吗?
  • @manzur List list = Collections.synchronizedList(new ArrayList()); 返回由指定列表支持的同步(线程安全)列表。
  • 感谢您的回答。所以我不需要在同步块中编写删除指令?
  • 我仍然得到异常,但也许我测试我的代码的方式是错误的。由于异常只偶尔发生一次,我发现测试修复的唯一方法是每 5 毫秒生成一个纸杯蛋糕,限制大小为 10 000
【解决方案3】:

如果底层数组已更改,则不得使用 IteratorArrayList

public static void main(String[] args) {
    List<Object> l = new ArrayList<>();
    Iterator<Object> i = l.iterator();
    l.add(new Object());
    try {
        System.out.println("I've added " + i.next());
    } catch (ConcurrentModificationException e) {
        System.out.println("Nooo!!!");
    }
}

当然,线程也是如此。

因此,在您的情况下,您正在检查吃过的纸杯蛋糕(以删除它们),同时还将新的纸杯蛋糕添加到同一个集合中。您正在经历的是所谓的竞态条件。

解决此问题的一种方法是使用@Bohemian 建议的CopyOnWriteArrayList,但我不建议这样做,因为每次修改都会导致创建新数组,这是性能开销(如果“旧" 数组仍在被引用,例如由于 Iterator 实例)。

解决这个问题的另一种方法是在更新方法中使用计数循环。

public void update() {
    List<Cupcake> cake = back.getCake();
    for (int i = 0; i < cake.size(); ++i) {
        Cupcake cupcake = cake.get(i);
        checkCollisionCup(cupcake);
        if (cupcake.isEaten()) 
            cake.remove(i);
    }
}

重要提示:使用此计数 for 循环,您应该使用 @Aalkhodiry 建议的同步列表。

Fwiw:但是,线程不止于此......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-01
    • 2022-10-23
    • 2016-12-27
    • 1970-01-01
    • 1970-01-01
    • 2020-09-05
    相关资源
    最近更新 更多