Найти тему
Nuances of programming

Синхронизация в Java. Часть 2

Оглавление

09.07.2020

Часть 1, Часть 2

Состояние гонки

Вновь приветствую вас в теме “Синхронизация в Java”! Надеюсь, что вы прочли мою предыдущую статью.

Давайте разберёмся, что же такое состояние гонки. Это состояние проявляется, когда нам нужно обратиться к данным параллельно. Хорошо, тогда что же значит параллельное обращение к данным? Проще говоря, это означает, что два разных потока могут считывать одну и ту же переменную, поле, или даже массив, определённые внутри класса Java. Давайте возьмём популярный шаблон проектирования “Singleton” и посмотрим, как в нём проявляется такое состояние гонки.

Два потока пытаются выполнить этот блок кода. Представьте себе ситуацию, где поток 1 (T1) задерживается в блоке if, в это время в процесс включается T2 и в итоге завершает блок if созданием “статического экземпляра Singleton”, после чего опять запускается T1 и уничтожает только что созданный T2 экземпляр.

Как же этого избежать? В этом случае помогает синхронизация, которая не даёт выполнять блок кода более чем одному потоку одновременно.

Значит синхронизация решит проблему? Да, именно так. Теперь давайте посмотрим её в действии на примере образов. В них мы увидим, как ключевое слово synchronized защищает методы.

Для большей наглядности я подготовил серию рисунков. Образ человека будет представлять собой поток. Взгляните на то, как этот человек просит ключ и попадает с его помощью в метод, а затем, покидая этот метод, возвращает ключ обратно. Поэтому другой человек (поток 2) должен дождаться, чтобы также получить ключ. Достаточно простой принцип, не так ли?

-2

По факту из сказанного следует, что нам нужен объект, который будет содержать ключ, делая подобную синхронизацию возможной. В случае, приведённом выше, мы поместили ключевое слово synchronized в public static method. А что же для преодоления блокировки в таком случае использует JVM? Объект Singleton.class. Т.е. схожим образом в случае синхронизации в нестатических методах JVM использует в качестве объекта синхронизации конкретный экземпляр, в котором Singleton.class находится.

public synchronized String getName() {
return this.name;
}

Давайте используем для синхронизации явный объект

Мы можем использовать для выполнения синхронизации явный объект, как это показано в блоке кода ниже. Да, достаточно только самого класса объекта. Я думаю, что вы уже знаете почему. Вместо синхронизирования метода getName() мы можем использовать синхронизированный блок внутри этого метода и передать объект key в качестве параметра ключевого слова synchronized. Помните, что это всегда будет удачным решением.

Синхронизация более чем одного метода

Предположим, что у нас есть класс Student с двумя синхронизированными методами getName() и getMarks(). Объект блокировки, используемый JVM, находится в самом объекте Student. Когда конкретный поток захочет выполнить getName(), он возьмёт этот объект блокировки, тем самым лишая другой поток возможности выполнить этот же метод одновременно с ним. Поскольку мы не объявляли явный объект в синхронизации наших методов, будет использован тот же объект key. Итак, теперь становится понятно, что для независимого выполнения этих двух методов в одно и то же время нам нужно создать в классе Student два объекта блокировки и синхронизировать эти два блока кода из 2 блокировок (2 разных объектов).

Теперь предположим, что у нас есть два экземпляра класса Student: Student1 и Student2. Синхронизирование более одного метода заблокирует объекты двумя ключами.

-3

Потоку, выполняющему getName(), объект Student1 не мешает выполнить getMarks() в объекте Student2.

-4

И он не пересечётся с другим потоком, который будет затем выполнять тот же метод getName() в объекте Student2. Думаю, что приведённые рисунки достаточно наглядно это демонстрируют.

-5

А что, если мы хотим помешать двум потокам выполнять метод getName() одновременно во всех экземплярах класса Student?

Теперь вы понимаете, для чего нам нужен объект блокировки, который привязан ни к одному из экземпляров класса Student, а к самому классу, не так ли? В таком случае это должно быть поле Static самого класса Student.

-6

Теперь становится ясно, что поток, который выполняет Student1, удерживая ключ, не даст другим потокам выполнять методы в экземпляре Student2 класса Student.

-7

ReEntrant Lock (блокировка с повторным входом)

Снова представим, что у нас есть два экземпляра класса Student с несколькими блоками synchronized.

Одни и те же ключи должны открывать одни и те же замки, согласны?

-8

В этих двух экземплярах method1() и method3() защищены одной и той же красной блокировкой, и вам нужен именно красный ключ. То же самое касается method4() и method1(). Итак, поток, выполняющий method1 в объекте Student1, получает ключ, и в определённый момент он войдёт в синхронизированный method3 экземпляра Student2. Поскольку этот поток уже имеет правильный ключ, то он будет допущен к выполнению и другого метода.

Когда поток удерживает блокировку, он может войти в блок, синхронизированный с этой блокировкой.

Благодарю вас за чтение!

Читайте также:

Читайте нас в телеграмме и vk

Перевод статьи Sachintha Hewawasam: Java Synchronization- part 2