Представьте себе случай, когда у нас есть две функции.

Один увеличивает значение поля счетчика, а другой уменьшает значение.
Для этого мы хотим использовать многопоточность.

ОС будет пытаться вытеснить и запланировать два потока самостоятельно для оптимизации. Поэтому мы не можем полагаться на порядок выполнения потоков.

Но есть проблема с приведенным выше фрагментом кода.

Проблема 1: мы используем два потока для доступа к общей памяти (объект CommonClass = new CommonClass () выделяется в куче и, следовательно, поле счетчика является общим),
здесь возникает проблема, поскольку мы не уверены в значении счетчика.
Может случиться, что поток 1 увеличил значение дважды, а поток 2 уменьшился только один раз.

.

Проблема 2: у нас здесь неатомарные операции, то есть this.counter ++ выполняет три действия: считывает значение счетчика, увеличивает его и записывает.
Таким образом, мы можем перезаписать работу, выполненную одним потоком, или прочитать несогласованное значение, потому что переключение контекста потока может произойти в любое время.

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

Есть два основных способа решить проблему:

Решение 1. Синхронизируйте потоки, используя синхронизированное ключевое слово для метода.

Мы знаем, что функции incrementValue и DecmentValue являются здесь важными секциями. Они выполняют неатомарные операции с разделяемой частью памяти.
Итак, мы можем использовать ключевое слово synchronized для функции. Это заблокирует поток для входа в критические разделы (incrementValue и DecmentValue), когда другой поток находится внутри него.

Но как это работает?
Оба метода действительно являются частью одного и того же объекта (объекта), и два синхронизированных блока (incrementValue и DecmentValue)
синхронизируются с одним и тем же объектом (объект 'this').

Можем ли мы сделать лучше?
Да, вместо синхронизации всего метода, мы можем синхронизировать только критическую секцию, используя внешние блокировки.

.

Решение 2. Синхронизируйте потоки с помощью внешних блокировок

Мы можем создать объект (любой объект) и использовать его как внешнюю блокировку.
Здесь мы можем использовать ключевое слово synchronized для блокировки.
Это заблокирует поток для входа в критические разделы (incrementValue и DecmentValue) когда внутри него находится другой поток.

Но как это работает?
Оба метода действительно являются частью одного и того же объекта (объекта), и два синхронизированных блока синхронизируются с одним и тем же объектом (блокировка ).
Даже синхронизированное ключевое слово для метода - не что иное, как синхронизированный объект класса 'this'.

Давайте сравним два решения друг с другом в примерах, приведенных ниже.

.

.

.

Пример 1
public synchronized void incrementValue () {
this.counter ++;
}

Пример 2
public void incrementValue () {
synchronized (this) {this.counter ++;}
}

Решение 3. Синхронизируйте потоки с помощью разных блокировок

Мы можем синхронизировать потоки, используя разные блокировки.
Представьте себе два счетчика (counter1 и counter2), и мы хотим увеличить один и уменьшить другой.
Здесь мы можем использовать многопоточность, чтобы выполнять их одновременно или параллельно.

Мы используем два потока для доступа к общей памяти (объект CommonClass = new CommonClass () выделяется в куче)
Мы можем использовать две блокировки для двух разделов.

Но как это работает?
Оба метода действительно являются частью одного и того же объекта (объекта), однако два синхронизированных блока (incrementValueCounter1 и DecmentValueCounter2),
синхронизируются на разные объекты (lock1 и lock2) и, следовательно, не будут влиять друг на друга.

Фактически в этом случае нам даже не нужны блокировки или многопоточность.

.

.

Решение 4. Синхронизируйте потоки для разных объектов

Мы используем два потока для доступа к не разделяемой памяти в куче.
object1 и object2 не зависят от кучи.

Но как это работает?
Метод действительно является частью разных объектов (object1 и object2), поэтому синхронизированный блок (incrementValue для обоих объектов)
синхронизирует на разных объектах (this для object1 и this для object2) и, следовательно, не будут влиять друг на друга.

Фактически в этом случае нам даже не нужны блокировки.

Следите за этой серией, чтобы узнать больше о многопоточности, параллелизме и параллельном программировании на Java.

Подробнее о параллельном программировании здесь

Блокировки в Java: https://medium.com/analytics-vidhya/understanding-java-thread-synchronization-with-methods-vs-objects-vs-locks-5428e3342fee

Повторные блокировки: https://medium.com/analytics-vidhya/unlock-the-power-of-reentrant-lock-in-java-d55ae9135443