Представьте себе случай, когда у нас есть две функции.
Один увеличивает значение поля счетчика, а другой уменьшает значение.
Для этого мы хотим использовать многопоточность.
ОС будет пытаться вытеснить и запланировать два потока самостоятельно для оптимизации. Поэтому мы не можем полагаться на порядок выполнения потоков.
Но есть проблема с приведенным выше фрагментом кода.
Проблема 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