При работе с объектами, доступ к которым осуществляется несколькими потоками, нужно обратить внимание на две вещи:
- Состояние гонки - из-за планирования потоков операционной системой и оптимизации переупорядочения команд компилятором инструкции выполняются в порядке, не предназначенном для программиста, что приводит к ошибкам
- Видимость памяти - В многопроцессорной системе изменения, сделанные одним процессором, не всегда сразу видны другим процессорам. Процессоры хранят данные в своих локальных регистрах и кэшах по соображениям производительности и поэтому невидимы для потоков, выполняемых другими процессорами.
К счастью, мы можем справиться с обеими этими ситуациями, используя правильную синхронизацию.
Давайте поговорим об этой конкретной программе.
Localdate
сам по себе является неизменным и потокобезопасным классом. Если мы посмотрим на исходный код этого класса, то увидим, что все поля этого класса final
. Это означает, что как только конструктор Localdate
завершит инициализацию объекта, сам объект станет видимым в потоках. Но когда она присваивается ссылочной переменной в другом объекте, то нужно ли присваивать (другими словами, содержимое ссылочной переменной) другим потокам или нет, это то, что нам нужно искать.
Учитывая конструктор в вашем случае, мы можем обеспечить видимость поля date
в потоках при условии date
либо final
, либо volatile
. Поскольку вы не изменяете поле date
в своем классе, вы вполне можете сделать его окончательным, и это обеспечит безопасную инициализацию. Если впоследствии вы решите использовать метод установки для этого поля (в зависимости от вашей бизнес-логики и вашего дизайна), вам нужно сделать поле volatile
вместо final
. volatile
создает отношение случай-до , что означает, что любая инструкция, которая выполняется в конкретном потоке перед записью в переменную volatile
, будет сразу же видна другим потокам, как только они прочитают то же самое переменная переменная.
То же самое относится к ConcurrentHashMap
. Вы должны сделать поле schedule
final
. Поскольку ConcurrentHashMap
содержит в себе все необходимые синхронизации, любое значение, установленное для ключа, будет видно другим потокам, когда они попытаются его прочитать.
Обратите внимание, однако, что если бы у вас были некоторые изменяемые объекты в качестве значений ConcurrentHashMap
вместо Boolean
, вам пришлось бы проектировать их так же, как указано выше.
Также может быть полезно знать, что существует концепция, называемая piggy-backing , которая означает, что если один поток записывает все свои поля, а затем записывает в переменную volatile
, все записывается поток перед записью в переменную volatile
будет виден другим потокам, при условии, что другие потоки сначала прочитают значение переменной volatile
после ее записи первым потоком. Но когда вы делаете это, вы должны очень внимательно следить за последовательностью чтения и записи, и это подвержено ошибкам. Итак, это делается, когда вы хотите выжать последнее падение производительности из фрагмента кода, который встречается редко. Позитивная безопасность, ремонтопригодность, удобочитаемость до исполнения.
Наконец, в коде нет условия гонки. Единственная запись, которая происходит, происходит на ConcurrentHashMap
, который сам по себе является потокобезопасным.