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


90

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

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

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);
    }

我需要知道的是,我使用的是正确的方法吗?如果不正确,应该使用什么使我的代码具有线程安全性?谢谢您的帮助!


2
(请注意,该List接口还不够完整,不足以在多线程中非常有用。)
Tom Hawtin-更新时间:2010年

3
我想指出的是,没有Collections.synchronizedList(),我们这里的比赛条件是真实的:P
Dylan Watson 2014年

Answers:


145

使用Collections.synchronizedList()

例如:

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

2
谢谢!我不确定为什么我不认为只使用Vector,因为我记得在某个地方阅读过它们是同步的。
ericso 2010年

32
也许使用定义为不推荐使用的类不是一个好主意
frandevel 2013年

1
尽管Vector很老,并且缺少Collections支持,但不建议弃用。像其他人在这里所说的那样,最好使用Collections.synchronizedList()。
阿斯图里奥

14
-1表示意见。不推荐使用vector,它如何不具有集合支持?它实现List。Vector的javadoc专门说:“从Java 2平台v1.2开始,对该类进行了改进以实现List接口,使其成为Java Collections Framework的成员。与新的collection实现不同,Vector是同步的。” 不使用Vector可能有充分的理由(避免同步,更改实现),但“过时”或“不现代”并不是其中之一。
fool4jesus 2013年

1
使用以下方法:Collections.synchronizedList(list); Collections.synchronizedSet(set); Collections.synchronizedMap(map); 上面的方法将collection作为参数并返回相同类型的collection,这些类型是同步的并且是线程安全的。
Sameer Kazi 2015年

35

更改

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,它是同步的,但这可能是我要做的。


1
或者List可能会更有用。或者List<RaceCar>
Tom Hawtin-抢险

好点,将其设为私有List finishOrder = Collections.synchronizedList(...)
Rondon Gonzo

我尝试了这个,编译器现在抱怨我在Collection上调用ArrayList方法: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); 就是说找不到get(0)方法。有什么想法吗?
ericso 2010年

很抱歉删除并重新添加我的评论。我正在尝试使突出显示能够使用反引号进行工作。我收到有关此类资料的强迫症。
ericso 2010年

不,那是行不通的。它不会将Collection强制转换为List:Race.java:41:发现不兼容的类型:java.util.Collection是否必需:java.util.List finishOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));
ericso'3

11

CopyOnWriteArrayList

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


3
考虑这堂课时请三思。引用类文档:“这通常太昂贵了,但是当遍历操作远远超过变异时,它可能比其他方法更有效,并且在您无法或不想同步遍历但又需要排除并发线程之间的干扰时很有用。” 另外,请参见CopyOnWriteArrayList和syncedList之间的区别
Basil Bourque

当您很少修改列表,但经常遍历元素时,此类便会发挥作用。例如,当您有一组侦听器时。您注册它们,然后进行大量迭代...,如果您不是显式需要列表接口,而是将修改和读取操作并发,请考虑一下ConcurrentLinkedQueue
benez

7

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

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


那是个很好的观点。这只是我用来学习Java的文字的练习。关键是要学习如何使用线程,而实际上我在构建一种机制来记录获奖者时超出了问题的原始规范。我虽然要使用计时器来衡量获胜者。但是说实话,我认为我已经从锻炼中得到了所需的东西。
ericso 2010年

5

您也可以将synchronized关键字用于这样的addFinisher方法

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

因此,您可以通过这种方式使用ArrayList add方法线程安全。


4
好吧,但是如果您有两种方法:addFinisher和delFinisher,该怎么办?这两种方法都是线程安全的,但是由于两者都访问同一个ArrayList,因此仍然会遇到麻烦。
masi 2014年

1
@masi然后,您final Object每次Collection以任何方式访问时,只需同步一个即可。
mkuech

2

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

您可以执行Collections.synchronizedCollection(任何集合对象),但请记住此经典同步。技术很昂贵,并且会带来性能开销。java.util.concurrent。*包的成本较低,并且可以通过使用类似的机制来更好地管理性能

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

因此,最好从java.util.concurrent。*包中获取一些东西


1

您也可以改为使用Vector,因为vector是线程安全的,而arraylist不是。尽管矢量很旧,但它们可以轻松解决您的目的。

但是您可以使Arraylist像下面的代码一样同步:

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

-1

您可以从ArrayList更改为Vector类型,在其中每种方法都将同步。

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

5
如果您建议使用其他集合,则Vector可能是一个糟糕的选择。它是一个遗留集合,已对新的Java集合框架的设计进行了改进。我确信java.until.concurrent包中有更好的选择。
Edwin Dalorzo 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.