Я обдумывал это и читал, но могу найти абсолютный авторитетный ответ.
У меня есть несколько глубоких структур данных, составленных из объектов, содержащих ArrayLists, Строки и примитивные значения. Я могу гарантировать, что данные в этих структурах не изменятся (никакой поток никогда не будет вносить структурные изменения в списки, ссылки изменения, примитивы изменения).
Я задаюсь вопросом, если чтение данных в этих структурах ориентировано на многопотоковое исполнение; т.е. действительно ли безопасно рекурсивно считать переменные из объектов, выполнить итерации ArrayLists и т.д. для извлечения информации из структур в нескольких потоках без синхронизации?
Единственная причина, по которой это было бы небезопасно, - это если бы один поток записывал в поле, а другой поток одновременно читал из него. Если данные не изменяются, не существует состояния гонки . Сделать объекты неизменяемыми - это один из способов гарантировать их потокобезопасность. Начните с чтения этой статьи от IBM.
Просто в качестве дополнения к ответам остальных: если вы уверены, что вам нужно синхронизировать списки массивов, вы можете вызвать Collections.synchronizedList(myList), который вернет вам потокобезопасную реализацию.
Я не вижу, как чтение из списков массивов, строк и примитивных значений с использованием нескольких потоков может быть проблемой.
Пока вы только читаете, синхронизация не нужна. Для строк и примитивов это, конечно, безопасно, поскольку они неизменяемы. Для ArrayLists это должно быть безопасно, но я не имею на это полномочий.
Члены группы ArrayList
не защищены какими-либо барьерами памяти, поэтому нет гарантии, что их изменения будут видны между потоками. Это применимо даже тогда, когда единственное «изменение», которое когда-либо вносилось в список, - это его построение.
Любые данные, которые совместно используются потоками, нуждаются в «барьере памяти» для обеспечения их видимости. Есть несколько способов добиться этого.
Во-первых, любой член, объявленный final
и инициализированный в конструкторе, становится видимым для любого потока после завершения конструктора.
Изменения любого члена, который объявлен volatile
, видны всем потокам. Фактически, запись «сбрасывается» из любого кэша в основную память, где ее может увидеть любой поток, обращающийся к основной памяти.
Теперь все становится немного сложнее. Любые записи, сделанные потоком до , который этот поток записывает в изменчивую переменную, также сбрасываются. Аналогичным образом, когда поток читает изменчивую переменную, его кэш очищается, и последующие чтения могут повторно заполнить его из основной памяти.
Наконец, синхронизированный
блок подобен энергозависимому чтению и записи с добавленным качеством атомарности. При получении монитора кэш чтения потока очищается. Когда монитор освобождается, все записи сбрасываются в основную память.
Один из способов сделать эту работу - заставить поток, заполняющий вашу совместно используемую структуру данных, назначить результат изменчивой
переменной (или AtomicReference
, или другому подходящему ] объект java.util.concurrent
). Когда другие потоки обращаются к этой переменной, они не только гарантированно получат самое последнее значение для этой переменной, но также и любые изменения, внесенные потоком в структуру данных до того, как он присвоил значение переменной.
Если данные никогда не изменяются после создания, все должно быть в порядке, и чтение будет потокобезопасным.
На всякий случай можно сделать все элементы данных "окончательными" и сделать все функции доступа реентерабельными, где это возможно; это обеспечивает потокобезопасность и может помочь сохранить потокобезопасность вашего кода, если вы измените его в будущем.
В общем, создание как можно большего числа членов "окончательными" помогает уменьшить количество ошибок, поэтому многие люди рекомендуют это как лучшую практику Java.
НЕ НЕ используйте java.util.Vector
, используйте java.util.Collections.unmodifiableXXX ()
оболочка, если они действительно не подлежат изменению, это гарантирует, что они не изменятся, и обеспечит соблюдение этого контракта. Если они собираются быть изменены, используйте java.util.Collections.syncronizedXXX ()
. Но это гарантирует только безопасность внутренней резьбы. Создание переменных final
также поможет компилятору / JIT в оптимизации.