JMM: ошибки многопоточного доступа к данным
Принято считать, что многопоточность (multithreading) одна из самых сложных тем в программировании. В первой заметке мы постарались ответить на вопрос, почему так много разработчиков делают ошибки при создании приложений, которые работают более чем в одном потоке. В этой заметке разберём типы ошибок многопоточного доступа к данным: race condition и memory consistency errors. Но, перед тем как обсуждать ошибки доступа, давайте сначала разберёмся, что такое многопоточный доступ к данным.
В какой именно ситуации можно получить такие ошибки?
Мы уже упоминали, что поток в Java — это объект. У любого объекта есть класс. В классе могут быть методы и переменные. Представьте, что у вас есть класс-наследник от Thread, в котором вы в одной из переменных храните ссылку на массив. И этот массив вы получаете в конструкторе класса.
Пусть теперь, в runtime вы создаёте два объекта рассмотренного выше класса и передаёте в оба один и тот же массив. К элементам этого массива вы можете обращаться в методах run() ваших объектов. Одновременно. Из разных потоков исполнения. Вот это и есть многопоточный доступ к данным, которые хранит массив. Например, один поток может писать что-то в массив, а другой читать из него.
Ошибки многопоточного доступа
Рассмотрим теперь ситуацию, когда два рассмотренных выше потока собираются увеличить значение числа, которое записано в первой ячейке массива. Последовательность событий такая: — первый поток прочитал значение и увеличил его, — второй поток прочитал то же значение, — первый записал новое, — второй увеличил значение и записал его.
В результате вместо ожидаемого увеличения значения на 2 мы получим увеличение на 1, так как результат работы первого потока был «перетёрт» результатом работы второго. Такая ситуация происходит из-за неатомарности операции увеличения значения числа. И является разновидностью ошибки многопоточного доступа — race condition.
Вторая возможная ошибка многопоточного доступа — memory consistency error происходит из-за того, что разные потоки могут быть физически исполнены на различных процессорах вашего компьютера. Каждый процессор может закэшировать значение переменной у себя, не записывая её в общую память. В результате разные потоки могут видеть в одно и тоже время разное значение переменной.
Описанные выше проблемы призвана решить Java Memory Model. Если не знаете, что это такое и как именно она может помочь Java-разработчику, спрашивайте в комментариях!