Подтвердить что ты не робот

Параллельные потоки, добавляющие в ArrayList в одно и то же время - что происходит?

У нас есть несколько потоков, вызывающих add(obj) на ArrayList.

Моя теория заключается в том, что когда add вызывается одновременно двумя потоками, то только один из двух добавленных объектов действительно добавляется к ArrayList. Это правдоподобно?

Если да, то как вам обойти это? Используйте синхронизированную коллекцию, например Vector?

4b9b3361

Ответ 1

Нет гарантированного поведения для того, что происходит, когда add вызывается одновременно двумя потоками в ArrayList. Тем не менее, мой опыт показывает, что оба объекта были добавлены в порядке. Большинство проблем безопасности потоков, связанных с списками, связаны с итерацией при добавлении/удалении. Несмотря на это, я настоятельно рекомендую использовать vanilla ArrayList с несколькими потоками и одновременным доступом.

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

Также я настоятельно рекомендую Java Concurrency на практике Goetz et al, если вы собираетесь тратить время на работу с потоками в Java. Книга более подробно освещает этот вопрос.

Ответ 2

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

В качестве альтернативы вы можете использовать Vector, вы можете использовать Collections.synchronizedList, вы можете использовать CopyOnWriteArrayList, или вы можете использовать отдельный замок. Все зависит от того, что еще вы делаете, и какого контроля у вас есть доступ к коллекции.

Ответ 3

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

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

Ответ 4

Поведение, вероятно, undefined, поскольку ArrayList не является потокобезопасным. Если вы измените список, когда итератор взаимодействует с ним, вы получите исключение ConcurrentModificationException. Вы можете обернуть ArrayList с помощью Collection.synchronizedList или использовать поточно-безопасную коллекцию (их много) или просто поместить вызовы добавления в синхронизированный блок.

Ответ 5

вы можете использовать List l = Collections.synchronizedList(new ArrayList());, если хотите потокобезопасную версию arrayList.

Ответ 6

Я придумал следующий код, чтобы воспроизвести сценарий реального мира.

100 задач выполняются параллельно, и они обновляют свой статус до основной программы. Я использую CountDownLatch для ожидания завершения задачи.

import java.util.concurrent.*;
import java.util.*;

public class Runner {

    // Should be replaced with Collections.synchronizedList(new ArrayList<Integer>())
    public List<Integer> completed = new ArrayList<Integer>();

    /**
     * @param args
     */
    public static void main(String[] args) {
        Runner r = new Runner();
        ExecutorService exe = Executors.newFixedThreadPool(30);
        int tasks = 100;
        CountDownLatch latch = new CountDownLatch(tasks);
        for (int i = 0; i < tasks; i++) {
            exe.submit(r.new Task(i, latch));
        }
        try {
            latch.await();
            System.out.println("Summary:");
            System.out.println("Number of tasks completed: "
                    + r.completed.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        exe.shutdown();
    }

    class Task implements Runnable {

        private int id;
        private CountDownLatch latch;

        public Task(int id, CountDownLatch latch) {
            this.id = id;
            this.latch = latch;
        }

        public void run() {
            Random r = new Random();
            try {
                Thread.sleep(r.nextInt(5000)); //Actual work of the task
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            completed.add(id);
            latch.countDown();
        }
    }
}

Когда я запустил приложение 10 раз и по крайней мере 3 - 4 раза, программа не напечатала правильное количество завершенных задач. В идеале он должен печатать 100 (если исключений не бывает). Но в некоторых случаях это была печать 98, 99 и т.д.

Таким образом, это доказывает, что параллельные обновления ArrayList не дадут правильных результатов.

Если я заменил ArrayList версией Синхронизированная, программа выведет правильные результаты.

Ответ 7

java.util.concurrent имеет список массивов с потоком. Стандартный ArrayList не является потокобезопасным, а поведение при одновременном обновлении нескольких потоков - undefined. Также могут быть нечетные поведения с несколькими читателями, когда один или несколько потоков записываются одновременно.

Ответ 8

http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html

Обратите внимание, что эта реализация не синхронизирована. Если несколько потоков обращаются к экземпляру ArrayList одновременно, и по крайней мере один из потоков изменяет список структурно, он должен быть синхронизирован извне.

Поскольку внутренняя синхронизация отсутствует, то, что вы теоретизируете, не правдоподобно.

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

Ответ 9

Вы можете использовать вместо ArrayList();:

Collections.synchronizedList( new ArrayList() );

или

new Vector();

synchronizedList как мне предпочтительнее, потому что это:

  • быстрее на 50-100%
  • может работать с уже существующим ArrayList