одновременные обновления массива в java

#java #concurrency

#java #параллелизм

Вопрос:

в Java у меня есть большой массив строк.

У меня есть один поток, делающий что-то вроде этого:

 for (int i=0;i<10000;i  ) array[i] = getSomeValue();
  

У меня есть другой поток, делающий что-то подобное:

 for (int i=10000;i<20000;i  ) array[i] = getSomeValue();
  

и другой поток, выполняющий:

 for (int i=20000;i<30000;i  ) array[i] = getSomeValue();
  

и так далее.

должен ли я делать что-то особенное для выполнения этой операции?

будет ли это работать?

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

Я работаю с 64-разрядной машиной с 16 процессорами и всем прочим.

Комментарии:

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

Ответ №1:

Ваш код будет работать нормально.

Разные части массива независимы друг от друга.

В спецификации говорится:

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

Комментарии:

1. не совсем верно: это зависит от того, что он хочет сделать с массивом, когда закончит (нужно убедиться, что обновления будут видны будущим потребителям массива).

2. Это отдельная проблема, jtahlborn.

3. @DJClayworth — почему это отдельная проблема? вопрос в том, корректен ли этот код. для меня «правильный» означает, что вы можете использовать результаты в своем коде. единственный способ использовать результаты кода оператора в его программе — это если он правильно обрабатывает видимость потока. довольно бесполезно инициализировать массив, а затем не иметь возможности прочитать его позже.

4. @DJClayworth это зависит от того, каково определение операционной системы для «работы». «Будет ли это работать» — ответ «да», если после операций записи не возникает проблем с видимостью памяти, и «нет», если они возникают.

Ответ №2:

Это должно работать нормально. Однако, если вы хотите быть уверены, что это безопасно, вы можете заполнить разные массивы в каждом потоке, а затем System.arraycopy() их в один большой массив.

Ответ №3:

вы можете безопасно инициализировать массив с помощью этого кода, однако любой код, который впоследствии должен использовать массив, должен быть корректно синхронизирован с потоками, выполняющими начальные обновления. это может быть так же просто, как «объединить» все потоки инициализации перед использованием массива.

Ответ №4:

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

Будет ли это на самом деле быстрее, зависит от настройки вашего оборудования и реализации потоков.

Ответ №5:

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

Одна из проблем, с которой вы можете столкнуться, связана с видимостью. Я не верю, что элементы массива гарантированно будут видны всем потокам, потому что они не являются изменчивыми. Итак, если к разделам массива необходимо получить доступ нескольким потокам, тогда у вас может возникнуть проблема. Один из способов справиться с этим — использовать AtomicIntegerArray… AtomicReferenceArray.

Ответ №6:

С Java 8 это стало намного проще:

 Arrays.parallelSetAll(array, i -> getSomeValue());
  

Это также должно решить проблемы видимости, упомянутые в других ответах и комментариях.