Как сделать мой ArrayList потокобезопасным? Другой подход к проблеме в Java?

91

У меня есть список ArrayList, который я хочу использовать для хранения объектов RaceCar, расширяющих класс Thread, как только они закончат выполнение. Класс с именем Race обрабатывает этот список ArrayList с помощью метода обратного вызова, который объект RaceCar вызывает по завершении выполнения. Метод обратного вызова addFinisher (RaceCar Finisher) добавляет объект RaceCar в ArrayList. Это должно указать порядок, в котором потоки завершают выполнение.

Я знаю, что ArrayList не синхронизируется и, следовательно, не является потокобезопасным. Я попытался использовать метод Collections.synchronizedCollection (c Collection), передав новый ArrayList и назначив возвращенную коллекцию 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интерфейс на самом деле недостаточно полный, чтобы быть очень полезным в многопоточности.)
Том Хотин - tackline
3
Я просто хотел бы отметить, что без Collections.synchronizedList()этого у нас было бы РЕАЛЬНОЕ условие гонки: P
Дилан Уотсон
Проверьте эту ссылку programmerzdojo.com/java-tutorials/…
rishi007bansod

Ответы:

149

Используйте Collections.synchronizedList().

Пример:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())
Амир Афгани
источник
2
Благодаря! Я не уверен, почему я не подумал просто использовать вектор, поскольку я помню, как читал где-то, что они были синхронизированы.
ericso
32
Возможно, это не лучшая идея работать с классами, которые определены как устаревшие,
frandevel
1
Хотя Vector довольно старый и не имеет поддержки коллекций, он не является устаревшим. Вероятно, лучше использовать Collections.synchronizedList (), как говорили здесь другие люди.
Asturio 04
14
-1 за комментарии. Vector не является устаревшим, и как он не поддерживает коллекции? Он реализует List. В javadoc для Vector прямо говорится: «Начиная с платформы Java 2 v1.2, этот класс был модифицирован для реализации интерфейса List, что сделало его членом Java Collections Framework. В отличие от новых реализаций коллекций, Vector синхронизирован». Могут быть веские причины для отказа от использования Vector (отказ от синхронизации, изменение реализаций), но быть «устаревшим» или «несовременным» не входит в их число.
fool4jesus 01
1
Используйте следующие методы: Collections.synchronizedList (list); Collections.synchronizedSet (набор); Collections.synchronizedMap (карта); Вышеупомянутые методы принимают коллекцию как параметр и возвращают тот же тип коллекции, который синхронизирован и потокобезопасен.
Самир Кази
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)

Список - это супертип ArrayList, поэтому вам нужно указать это.

В остальном то, что вы делаете, кажется нормальным. Другой вариант - вы можете использовать вектор, который синхронизируется, но, вероятно, я бы так поступил.

Преподобный Гонзо
источник
1
Или List, наверное, было бы полезнее. Или List<RaceCar>.
Том Хотин - tackline
Хороший замечание, сделайте его частным. List finishOrder = Collections.synchronizedList (...)
Преподобный Гонзо,
Я пробовал это, и теперь компилятор жалуется на то, что я вызываю методы ArrayList в коллекции: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); он говорит, что метод get (0) не найден. Мысли?
ericso
Извините за удаление и повторное добавление моего комментария. Я пытался заставить выделение работать с помощью обратных кавычек. У меня ОКР из-за таких вещей.
ericso
Нет, не работает. Он не будет преобразовывать коллекцию в список: Race.java:41: обнаружены несовместимые типы: java.util.Collection требуется: java.util.List finishOrder = Collections.synchronizedCollection (new ArrayList (numberOfRaceCars));
ericso
12

CopyOnWriteArrayList

Используйте CopyOnWriteArrayListкласс. Это поточно-ориентированная версия ArrayList.

Сингх Пиюш
источник
5
Подумайте дважды, рассматривая этот класс. Процитируем документ класса: «Обычно это слишком дорого, но может быть более эффективным, чем альтернативы, когда операции обхода значительно превосходят количество мутаций, и полезно, когда вы не можете или не хотите синхронизировать обходы, но при этом необходимо исключить вмешательство между параллельными потоками. . » Также см. Разницу между CopyOnWriteArrayList и synchronizedList
Бэзил Бурк,
1
этот класс вступает в игру, когда вы редко изменяете список, но часто перебираете элементы. например, когда у вас есть набор слушателей. вы регистрируете их, а затем много повторяете ..., если вам явно не нужен интерфейс списка, но операции изменения и чтения должны быть параллельными, подумайтеConcurrentLinkedQueue
бенез
7

Вы могли бы использовать неправильный подход. Тот факт, что один поток, имитирующий автомобиль, завершается раньше, чем другой поток моделирования автомобиля, не означает, что первый поток должен выиграть имитацию гонки.

Это во многом зависит от вашего приложения, но может быть лучше иметь один поток, который вычисляет состояние всех автомобилей через небольшие промежутки времени, пока гонка не будет завершена. Или, если вы предпочитаете использовать несколько потоков, вы можете заставить каждую машину записывать «смоделированное» время, необходимое для завершения гонки, и выбирать победителя в качестве победителя с наименьшим временем.

Эриксон
источник
Неплохо подмечено. Это просто упражнение из текста, который я использую для изучения Java. Дело было в том, чтобы научиться использовать потоки, и я выхожу за рамки исходных спецификаций проблемы, создавая механизм для регистрации победителей. Я подумал об использовании таймера для измерения победителей. Но, честно говоря, я думаю, что получил от упражнения то, что мне нужно.
ericso
5

Вы также можете использовать synchronizedключевое слово для addFinisherтакого метода

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

Таким образом, вы можете использовать потокобезопасный метод добавления ArrayList.

Erhun
источник
4
ну а что, если у вас есть два метода: addFinisher и delFinisher? Оба метода потокобезопасны, но поскольку оба обращаются к одному и тому же списку ArrayList, у вас все равно возникнут проблемы.
omni
1
@masi Тогда вы просто синхронизируете final Objectвместо этого каждый раз, когда Collectionкаким-либо образом получаете доступ к .
mkuech
2

Если вы хотите использовать потокобезопасную версию объекта ant-collection, воспользуйтесь помощью пакета java.util.concurrent. * . В нем есть почти все параллельные версии несинхронизированных объектов коллекции. например: для ArrayList у вас есть java.util.concurrent.CopyOnWriteArrayList

Вы можете использовать Collections.synchronizedCollection (любой объект коллекции), но помните этот классический синхронизатор. техника дорогая и требует накладных расходов. Пакет java.util.concurrent. * дешевле и лучше управляет производительностью, используя такие механизмы, как

копирование при записи, сравнение и замена, блокировка, итераторы моментальных снимков и т. д.

Итак, предпочитайте что-то из пакета java.util.concurrent. *

Джайдип Рамеш Дешмук
источник
1

Вместо этого вы также можете использовать как Vector, поскольку векторы являются потокобезопасными, а arrayylist - нет. Хотя векторы старые, но они могут легко решить вашу задачу.

Но вы можете синхронизировать свой 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 Collections Framework. Я уверен, что в пакете java.until.concurrent есть варианты получше.
Эдвин Далорцо 08