如何使我的 ArrayList 线程安全? Java中解决问题的另一种方法?

Posted

技术标签:

【中文标题】如何使我的 ArrayList 线程安全? Java中解决问题的另一种方法?【英文标题】:How do I make my ArrayList Thread-Safe? Another approach to problem in Java? 【发布时间】:2011-01-27 11:47:13 【问题描述】:

我有一个 ArrayList,我想用它来保存 RaceCar 对象,这些对象在执行完 Thread 类后立即扩展。一个名为 Race 的类使用 RaceCar 对象在完成执行时调用的回调方法来处理此 ArrayList。回调方法 addFinisher(RaceCar finisher) 将 RaceCar 对象添加到 ArrayList。这应该给出线程完成执行的顺序。

我知道 ArrayList 不是同步的,因此不是线程安全的。我尝试通过传入一个新的 ArrayList 并将返回的 Collection 分配给一个 ArrayList 来使用 Collections.synchronizedCollection(c Collection) 方法。但是,这给了我一个编译器错误:

Race.java:41: incompatible types
found   : java.util.Collection
required: java.util.ArrayList
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

以下是相关代码:

public class Race implements RaceListener 
    private Thread[] racers;
    private ArrayList finishingOrder;

    //Make an ArrayList to hold RaceCar objects to determine winners
    finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

    //Fill array with RaceCar objects
    for(int i=0; i<numberOfRaceCars; i++) 
    racers[i] = new RaceCar(laps, inputs[i]);

        //Add this as a RaceListener to each RaceCar
        ((RaceCar) racers[i]).addRaceListener(this);
    

    //Implement the one method in the RaceListener interface
    public void addFinisher(RaceCar finisher) 
        finishingOrder.add(finisher);
    

我需要知道的是,我是否使用了正确的方法,如果没有,我应该使用什么来使我的代码线程安全?感谢您的帮助!

【问题讨论】:

(注意,List 接口还不够完整,在多线程中非常有用。) 我想指出的是,如果没有Collections.synchronizedList(),我们这里会有一个真正的竞争条件:P 查看此链接programmerzdojo.com/java-tutorials/… 【参考方案1】:

使用Collections.synchronizedList()

例如:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())

【讨论】:

谢谢!我不知道为什么我没有想到只使用 Vector,因为我记得在某个地方读到它们是同步的。 虽然 Vector 已经很老了并且缺乏 Collections 支持,但它并没有被弃用。像其他人在这里所说的那样使用 Collections.synchronizedList() 可能会更好。 -1 用于 cmets。 Vector 未被弃用,它如何不支持集合?它实现了列表。 Vector 的 javadoc 明确指出:“从 Java 2 平台 v1.2 开始,该类被改进为实现 List 接口,使其成为 Java Collections Framework 的成员。与新的集合实现不同,Vector 是同步的。”不使用 Vector 可能有充分的理由(避免同步、更改实现),但“过时”或“不现代”并不是其中之一。 使用以下方法:Collections.synchronizedList(list); Collections.synchronizedSet(set); Collections.synchronizedMap(map);上述方法以集合为参数,返回同类型的同步且线程安全的集合。 评论与答案无关,因为答案已被编辑,不再建议Vector【参考方案2】:

改变

private ArrayList finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars)

private List finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedList(new ArrayList(numberOfRaceCars)

List 是 ArrayList 的超类型,因此您需要指定它。

否则,您正在做的似乎很好。另一个选项是你可以使用 Vector,它是同步的,但这可能是我会做的。

【讨论】:

List 可能会更有用。或List&lt;RaceCar&gt;. 好点,将其设为私有 ListfinishOrder = Collections.synchronizedList(...) 我试过了,编译器现在抱怨我在 Collection 上调用 ArrayList 方法://Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); 它是说找不到 get(0) 方法。想法? 很抱歉删除并重新添加我的评论。我试图使用反引号使突出显示工作。我对那种东西有强迫症。 不,这行不通。它不会将集合转换为列表:Race.java:41:发现不兼容的类型:需要 java.util.Collection:java.util.ListfinishOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));【参考方案3】:

CopyOnWriteArrayList

使用CopyOnWriteArrayList 类。这是ArrayList 的线程安全版本。

【讨论】:

在考虑这门课时要三思。引用类文档:“这通常成本太高,但当遍历操作的数量远远超过突变时,它可能比替代方法更有效,并且当您不能或不想同步遍历但需要排除并发线程之间的干扰时很有用。”另请参阅Difference between CopyOnWriteArrayList and synchronizedList 这个类在你很少修改列表但经常迭代元素时发挥作用。例如当你有一组听众时。你注册它们然后你迭代很多......,如果你不明确需要列表接口,但修改和读取操作是并发的,考虑ConcurrentLinkedQueue【参考方案4】:

可能使用了错误的方法。仅仅因为一个模拟汽车的线程在另一个汽车模拟线程之前完成并不意味着第一个线程应该赢得模拟比赛。

这在很大程度上取决于您的应用程序,但最好有一个线程以较小的时间间隔计算所有汽车的状态,直到比赛结束。或者,如果您更喜欢使用多线程,您可以让每辆车记录完成比赛所用的“模拟”时间,并选择时间最短的获胜者。

【讨论】:

这是一个很好的观点。这只是我用来学习 Java 的文本中的一个练习。重点是学习如何使用线程,实际上我在构建一个记录获胜者的机制方面超出了问题的原始规范。我想用计时器来衡量获胜者。但老实说,我认为我已经从练习中得到了我需要的东西。【参考方案5】:

您也可以使用synchronized 关键字作为addFinisher 这样的方法

    //Implement the one method in the RaceListener interface
    public synchronized void addFinisher(RaceCar finisher) 
        finishingOrder.add(finisher);
    

所以你可以通过这种方式使用 ArrayList 添加方法线程安全。

【讨论】:

好吧,但是如果你有两个方法呢:addFinisher 和 delFinisher?这两种方法都是线程安全的,但由于它们都访问同一个 ArrayList,你仍然会遇到麻烦。 @masi 然后您只需在 final Object 上同步,而不是每次以任何方式访问 Collection 时。【参考方案6】:

当你想使用 ant 线程安全版本的 ant 集合对象时,请借助 java.util.concurrent.* 包。 它具有几乎所有并发版本的非同步集合对象。例如:对于 ArrayList,你有 java.util.concurrent.CopyOnWriteArrayList

您可以执行 Collections.synchronizedCollection(任何集合对象),但请记住这个经典的同步器。技术是昂贵的并且伴随着性能开销。 java.util.concurrent.* 包更便宜,并且通过使用像

这样的机制以更好的方式管理性能

写时复制、比较和交换、锁定、快照迭代器等。

所以,更喜欢 java.util.concurrent.* 包中的东西

【讨论】:

【参考方案7】:

您也可以使用 as Vector,因为向量是线程安全的,而 arraylist 不是。 虽然向量很旧,但它们可以轻松解决您的目的。

但是你可以让你的 Arraylist 像代码一样同步:

Collections.synchronizedList(new ArrayList(numberOfRaceCars())); 

【讨论】:

【参考方案8】:

您可以从 ArrayList 更改为 Vector 类型,其中每个方法都是同步的。

private Vector finishingOrder;
//Make a Vector to hold RaceCar objects to determine winners
finishingOrder = new Vector(numberOfRaceCars);

【讨论】:

如果您要建议使用另一个集合,Vector 可能是一个糟糕的选择。它是一个遗留集合,经过改造以适应新的 Java 集合框架的设计。我确信 java.until.concurrent 包中有更好的选择。

以上是关于如何使我的 ArrayList 线程安全? Java中解决问题的另一种方法?的主要内容,如果未能解决你的问题,请参考以下文章

我应该始终使我的 java 代码线程安全,还是出于性能原因仅在需要时才这样做?

ARRAYLIST如何保证线程安全

ArrayList如何保证线程安全

ArrayList如何实现线程安全

如何保证ArrayList线程安全

ArrayList如何实现线程安全