Многопоточное программирование в Java. Тимур Машнин
Читать онлайн книгу.действием.
Он состоит из следующих действий:
– Получить текущее значение.
– Увеличить полученное значение на 1.
– Сохранить увеличенное значение.
Поэтому очень простые выражения могут определять сложные действия, которые могут разлагаться на другие действия.
Но есть действия, которые являются атомарными:
Это чтение и запись всех переменных, ссылочных на объекты и примитивных переменных, за исключением переменных типа long и double.
Так как в Java 64-битные long и double значения рассматриваются как два 32-битных значения.
Это означает, что 64-разрядная операция записи выполняется как две отдельные 32-разрядные операции.
И это значит, что действия с long и double переменными не являются потокобезопасными.
Когда несколько потоков получают доступ к long или double значению без синхронизации, это может вызвать проблемы.
Чтобы обеспечить атомарность действий с long и double значениями можно использовать ключевое слово volatile.
Если переменная объявлена как volatile, это означает, что она может изменяться разными потоками.
Среда выполнения JRE неявно обеспечивает синхронизацию при доступе к volatile-переменным, но с очень большой оговоркой: чтение volatile-переменной и запись в volatile-переменную синхронизированы, а неатомарные операции, такие как операция инкремента или декремента ― нет.
Атомарные действия не могут перемешиваться, поэтому их можно использовать, не опасаясь интерференции потоков.
Однако это не устраняет необходимости синхронизации атомарных действий, так как возможны ошибки согласованности памяти.
В целях повышения производительности среда выполнения JRE сохраняет локальные копии переменных для каждого потока, который на них ссылается.
Такие «локальные» копии переменных работают как кэш и помогают потоку избежать обращения к главной памяти каждый раз, когда требуется получить значение переменной.
Поэтому если один поток изменит значение переменной, то другой поток может не увидеть это изменение, так как будет считывать значение из своего кэша. Это и будет ошибкой согласованности памяти
Однако если переменная помечена как volatile, то, когда бы поток не считывал значение, он будет считывать ее текущее значение.
Использование volatile переменных, не только для long и double, снижает риск ошибок согласованности памяти, поскольку любая запись в volatile переменную устанавливает связь между событиями и последующими чтениями этой же переменной.
Это означает, что изменения в volatile переменной всегда видны для других потоков.
Опять же речь идет только об операциях чтения и записи.
Резюмируя, объявление блока кода синхронным обеспечивает для кода атомарность и видимость.
Атомарность значит, что только один поток одновременно может выполнять код, защищенный данным объектом-монитором (блокировкой), позволяя предотвратить многочисленные потоки от столкновений друг с другом во время обновления общего состояния.
Видимость связана с особенностями