Основная проблема одновременного обновления изменяемых данных заключается в том, что потоки могут воспринимать значения переменных, происходящие из разных версий, то есть смесь старых и новых значений, когда речь идет об одном обновлении, образуется несогласованное состояние, нарушаются инварианты этих переменных..
См., Например, Java ArrayList
.В нем есть поле int
, содержащее текущий размер и ссылку на массив, элементы которого являются ссылками на содержащиеся объекты.Значения этих переменных должны соответствовать определенным инвариантам, например, если размер не равен нулю, ссылка на массив никогда не равна null
, а длина массива всегда больше или равна размеру.При просмотре значений различных обновлений для этих переменных эти инварианты больше не сохраняются, поэтому потоки могут видеть содержимое списка, которого никогда не было в этой форме, или приводить к ошибкам с ложными исключениями, сообщая о недопустимом состоянии, которое должно быть невозможным (например, NullPointerException
илиArrayIndexOutOfBoundeException
).
Обратите внимание, что поточно-ориентированные или параллельные структуры данных решают только проблему, касающуюся внутренних элементов структуры данных, поэтому операции больше не завершаются ошибочными исключениями (что касается состояния коллекции, мы немы уже говорили о состоянии содержащегося элемента), но операции, повторяющиеся над этими коллекциями или смотрящие на более чем один содержащийся элемент в любой форме, все еще подвержены возможному наблюдению противоречивого состояния относительно содержащихся элементов.Это также относится к антишаблону check-then-act, где приложение сначала проверяет условие (например, используя contains
), а затем воздействует на него (например, извлекает, добавляет или удаляет элемент), тогда как условие может изменитьсямежду ними.
Напротив, поток, работающий с неизменяемой структурой данных, может работать с устаревшей версией, но все переменные, принадлежащие этой структуре, согласуются друг с другом, отражая одну и ту же версию.При выполнении обновления вам не нужно думать об исключении других потоков, это просто не нужно, поскольку новые структуры данных не видны другим потокам.Вся задача публикации новой версии сводится к задаче публикации корневой ссылки на новую версию вашей структуры данных.Если вы не можете остановить другие потоки, обрабатывающие старую версию, самое худшее, что может случиться, это то, что вам, возможно, придется повторить операцию, используя новые данные впоследствии, другими словами, просто проблему производительности, в худшем случае.
Это гладко работает с языками программирования со сборкой мусора, поскольку они позволяют новой структуре данных ссылаться на старые объекты, просто заменяя измененные объекты (и их родителей), не беспокоясь о том, какие объектывсе еще используется, а который нет.