Вопросы для интервью: оптимистичная блокировка и пессимистическая блокировка

Java

предисловие

小孩子才做选择,我全都要, Сегодня я напишу содержание, которое необходимо задать в интервью: оптимистический замок и пессимистический замок. В основном из следующих аспектов:

  • Что такое оптимистическая блокировка
  • Что такое пессимистический замок
  • Общие реализации оптимистической блокировки
  • Общие реализации пессимистических блокировок
  • Недостатки оптимистической блокировки
  • Недостатки пессимистической блокировки

Пока писал статью, неожиданно получил сообщение от друга, что Узи вышел на пенсию, а плеер LPL0006 отключен. Я надеюсь, что вы сможете увидеть все цветы Чанъань за один день, пройти через горы и реки и вернуться тем молодым человеком, которым вы были раньше. Подойди и кричи со мной:大道至简-唯我自豪

1. Что такое оптимистическая блокировка?

Оптимистичные замки всегда предполагают, что дела идут в хорошем направлении, так же, как некоторые люди рождаются оптимистами и живут на солнце!

Оптимистическая блокировка всегда предполагает наилучшую ситуацию: каждый раз, когда вы идете за получением данных, она думает, что другие не будут ее модифицировать, поэтому она не будет заблокирована, но при обновлении будет судить, обновили ли другие данные за этот период. Оптимистическая блокировка подходит для приложений с множественным чтением, так как оптимистическая блокировка не блокирует при чтении данных, что снижает накладные расходы на блокировки и увеличивает общую пропускную способность системы. Даже если будет случайный конфликт, это не помешает, либо повторить коммит, либо вернуть пользователю, что обновление не удалось, конечно,前提是偶尔发生冲突, но если возникают частые конфликты, приложение верхнего уровня будет продолжать повторять прокрутку, что снизит производительность и не будет стоить потери.

2. Что такое пессимистическая блокировка

Пессимистический замок всегда предполагает, что дела идут в плохом направлении, например, некоторые люди пережили определенные вещи и могут не доверять другим, а доверять только себе, в темноте ступая на свет!

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

3. Распространенные реализации оптимистической блокировки

3.1 Механизм номера версии

Механизм номера версии заключается в добавлении поля в таблицу,version, При изменении записи сначала запрашивайте запись, а затем каждый раз при ее изменении добавляйте к значению поля 1. Условием оценки является только что запрошенное значение. См. следующий процесс, чтобы понять:

  • 3.1.1 Добавить таблицу с информацией о пользователе
CREATE TABLE `user_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_name` varchar(64) DEFAULT NULL COMMENT '用户姓名',
  `money` decimal(15,0) DEFAULT '0' COMMENT '剩余金额(分)',
  `version` bigint(20) DEFAULT '1' COMMENT '版本号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='用户信息表';
  • 3.1.2 Добавить новую часть данных
INSERT INTO `user_info` (`user_name`, `money`, `version`) VALUES ('张三', 1000, 1);
  • 3.1.3 Этапы работы
шаг нить А нить Б
1 Запросите данные Zhang San, получите номер версии 1 (SELECT * FROM user_info WHERE user_name = 'Zhang San';)
2 Запросите данные Zhang San, получите номер версии 1 (SELECT * FROM user_info WHERE user_name = 'Zhang San';)
3 Измените количество Zhang San, добавьте 100 и номер версии +1 (UPDATE user_info SET money = money + 100, version = version + 1 WHERE user_name = 'Zhang San' AND version = 1;), количество измененных баров возвращено 1
4 Измените количество Zhang San, добавьте 200 и номер версии +1 (UPDATE user_info SET money = money + 200, version = version + 1 WHERE user_name = 'Zhang San' AND version = 1;), количество измененных предметов возвращено 0
5 Судя по тому, равно ли количество измененных баров 0, возвращает отказ, иначе возвращает успех
6 Судя по тому, равно ли количество измененных баров 0, возвращает отказ, иначе возвращает успех
7 вернуть успех
8 вернуть отказ

3.2 Алгоритм CAS

CAS означает сравнение и обмен, что является хорошо известным алгоритмом без блокировки. Программирование без блокировок, то есть для достижения переменной синхронизации между несколькими потоками без использования блокировок (никакие потоки не блокируются), поэтому его также называют неблокирующей синхронизацией (Non-blocking Synchronization). Алгоритм CAS включает три операнда:

  1. Значение памяти V, которое необходимо прочитать и записать (значение переменной в основной памяти)
  2. Значение A для сравнения (клонирование значения переменной в локальной памяти потока)
  3. новое значение для записи B (новое значение для обновления)

CAS атомарно обновляет значение V новым значением B тогда и только тогда, когда значение V равно A , в противном случае ничего не делается (сравнение и замена — собственная атомарная операция). В общем, это спиновая операция, то есть непрерывная повторная попытка, см. следующий процесс:

  • 3.2.1 Алгоритм CAS имитирует данные обновления базы данных (таблица такая же, как и сейчас, начальное значение количества пользователей Zhang San равно 1000), а количество пользователей Zhang San увеличивается на 100:
private void updateMoney(String userName){
     // 死循环
     for (;;){
         // 获取张三的金额
         BigDecimal money = this.userMapper.getMoneyByName(userName);
         User user = new User();
         user.setMoney(money);
         user.setUserName(userName);
         // 根据用户名和金额进行更新(金额+100)
         Integer updateCount = this.userMapper.updateMoneyByNameAndMoney(user);
         if (updateCount != null && updateCount.equals(1)){
             // 如果更新成功就跳出循环
             break;
         }
     }
 }
  • 3.2.2 Блок-схема выглядит следующим образом:
шаг нить А нить Б
1 Запросите деньги Чжан Саня = 1000 из таблицы, установите значение для сравнения на 1000, а новое записываемое значение — это деньги + 100 = 1100 (V:1000--A:1000--B:1100).
2 Запросите деньги Чжан Саня = 1000 из таблицы, установите значение для сравнения на 1000, а новое записываемое значение — это деньги + 100 = 1100 (V:1000--A:1000--B:1100).
3 Обновите количество Zhang San (UPDATE user_info SET money = money + 100 WHERE user_name = 'Zhang San' AND money = 1000;), количество возвращенных обновлений равно 1
4 Обновите количество Zhang San (UPDATE user_info SET money = money + 100 WHERE user_name = 'Zhang San' AND money = 1000;), количество возвращенных обновлений равно 0
5 Выйти из цикла и вернуть успешное обновление
6 Спин снова запрашивает деньги Чжан Саня = 1100 из таблицы, устанавливает значение для сравнения на 1100, а новое записываемое значение — это деньги + 100 = 1200 (V:1100--A:1100--B:1200).
7 Обновите количество Zhang San (UPDATE user_info SET money = money + 100 WHERE user_name = 'Zhang San' AND money = 1100;), количество возвращенных обновлений равно 1
8 Выйти из цикла и вернуть успешное обновление

Увидев это, люди с проницательным взглядом обнаружили небольшие проблемы в обновлении CAS, а в чем проблема и как ее решить, я напишу ниже, иначе следующих статей писать не буду. . . . . .

Обратите внимание, что механизм нумерации версий здесь не совпадает с механизмом нумерации версий для проблем ABA в CAS.

4. Распространенные реализации пессимистичных блокировок

4.1 ReentrantLock

Повторные блокировки — это тип пессимистичных блокировок. Если вы читали первые две статьи, принцип повторных блокировок очень ясен. Если вы не уверены, посмотрите на следующий процесс:

  • Предположим, что значение состояния синхронизации равно 0, чтобы указать, что блокировка не заблокирована, а значение 1 успешно заблокировано.
шаг нить А нить Б
1 Скопируйте значение состояния синхронизации из основной памяти в 0, установите значение для сравнения в 0 и новое значение для записи в 1 (V:0--A:0--B:1)
2 Скопируйте значение состояния синхронизации из основной памяти в 0, установите значение для сравнения в 0 и новое значение для записи в 1 (V:0--A:0--B:1)
3 Обновите основную память, сравните значение A с основной памятью, 0 = 0, блокировка прошла успешно, и значение основной памяти в это время равно 1.
4 Обновите основную память, сравните значение A с основной памятью, 0 != 1, блокировка не удалась.
5 вернуть блокировку успешно
6 Выполнять бизнес-логику Spin снова пытается обновить основную память, сравнивает значение A с основной памятью, 0 != 1, блокировка не выполняется
7 Spin снова пытается обновить основную память, сравнивает значение A с основной памятью, 0 != 1, блокировка не выполняется
8 Вызовите метод parkAndCheckInterrupt(), чтобы заблокировать поток
9 Снимите блокировку и установите значение состояния синхронизации на 0.
10 Узел-предшественник удаляется из очереди, и после пробуждения попробуйте снова обновить основную память, сравните значение A с основной памятью, 0 = 0, блокировка прошла успешно, а значение основной памяти в это время равно 1.

Можно видеть, что пока поток A получает блокировку и не снимает ее, поток B не может получить блокировку. Если A не снимает блокировку, блокировку может получить B. Способ добавления блокировок — сравнение и обмен через CAS. . Он попытается вращаться, чтобы установить значение. После нескольких попыток поток будет заблокирован и снова попытается получить блокировку после того, как узел-предшественник будет исключен из очереди. Это также объясняет, почему пессимистичные блокировки потребляют больше производительности, чем оптимистичные блокировки.

4.2 synchronized

По сути, это похоже на предыдущее, за исключением того, что вышеприведенное само поддерживает переменную типа volatile int для описания получения и снятия блокировок, в то время как synchronized полагается на инструкции для определения блокировок и освобождения блокировок, как показано ниже:

public class synchronizedTest {
  
    。。。。。。

    public void synchronizedTest(){
        synchronized (this){
            mapper.updateMoneyByName("张三");
        }
    }
}

Блок-схема, соответствующая приведенному выше коду, выглядит следующим образом:

шаг нить А нить Б
1 Вызовите метод synchronizedTest()
2 Вызовите метод synchronizedTest()
3 Вставьте директиву monitorenter
4 Выполнять бизнес-логику Попытка стать владельцем директивы monitorenter
5 Выполнять бизнес-логику Попытка стать владельцем директивы monitorenter
6 Выполнять бизнес-логику Попытка стать владельцем директивы monitorenter
7 После выполнения бизнес-логики вставьте команду monitorexit Попытка получить право собственности на инструкцию monitorenter, получение выполнено успешно, вставьте инструкцию monitorenter
8 Выполнять бизнес-логику
9 Выполнять бизнес-логику
10 После выполнения бизнес-логики вставьте команду monitorexit

Если возникает исключение, когда поток выполняет метод synchronizedTest(), инструкция monitorexit будет вставлена ​​в исключение,ReentrantLockВам нужно вручную заблокировать и снять блокировку, иsynchronizedЭто JVM блокирует и снимает блокировки для вас.

5. Недостатки оптимистической блокировки

5.1.1 Проблемы АВА

Выше было сказано, что есть проблема при реализации оптимистической блокировки CAS, которую может найти проницательный человек, не знаю, находили ли вы, проблема в следующем:

шаг нить А нить Б
1 Запросите деньги Чжан Саня = 1000 из таблицы, установите значение для сравнения на 1000, а новое записываемое значение — это деньги + 100 = 1100 (V:1000--A:1000--B:1100).
2 Запросите деньги Чжан Саня = 1000 из таблицы, установите значение для сравнения на 1000, а новое записываемое значение — это деньги + 100 = 1100 (V:1000--A:1000--B:1100).
3 Обновите количество Zhang San (UPDATE user_info SET money = money + 100 WHERE user_name = 'Zhang San' AND money = 1000;), количество возвращенных обновлений равно 1
4 Обновите количество Zhang San (UPDATE user_info SET money = money + 100 WHERE user_name = 'Zhang San' AND money = 1000;), количество возвращенных обновлений равно 0
5 Выйти из цикла и вернуть успешное обновление
6 Спин снова запрашивает деньги Чжан Саня = 1100 из таблицы, устанавливает значение для сравнения на 1100, а новое записываемое значение — это деньги + 100 = 1200 (V:1100--A:1100--B:1200).
7 Обновите количество Zhang San (UPDATE user_info SET money = money + 100 WHERE user_name = 'Zhang San' AND money = 1100;), количество возвращенных обновлений равно 1 (обратите внимание, что проблема здесь, на шаге 6 мы запрашиваем money= 1100, и когда мы здесь судим, можем ли мы быть уверены, что деньги не были изменены другими потоками? Ответ - нет, возможно поток C увеличился на 100, поток D уменьшился на 100, а деньги значение здесь по-прежнему 1100 , эта проблема известна как проблема «ABA» операций CAS)
8 Выйти из цикла и вернуть успешное обновление
  • решение:

добавить таблицуversionполе, добавляйте 1 каждый раз, когда значение изменяется, чтобы вы могли судить, было ли изменено полученное значение при записи Блок-схема выглядит следующим образом:

шаг нить А нить Б
1 Запросить деньги Чжан Саня = 1000, версия = 1 из таблицы
2 Запросить деньги Чжан Саня = 1000, версия = 1 из таблицы
3 Обновите количество Zhang San (UPDATE user_info SET money = money + 100, version = version + 1 WHERE user_name = 'Zhang San' AND money = 1000 AND version = 1;), количество возвращенных обновлений равно 1
4 Обновите количество Zhang San (UPDATE user_info SET money = money + 100, version = version + 1 WHERE user_name = 'Zhang San' AND money = 1000 AND version = 1;), количество возвращенных обновлений равно 0
5 Выйти из цикла и вернуть успешное обновление
6 Спин снова запрашивает таблицу, чтобы найти деньги Чжан Саня = 1100, версия = 2.
7 Обновите количество Zhang San (UPDATE user_info SET money = money + 100, version = version + 1 WHERE user_name = 'Zhang San' AND money = 1100 AND version = 2;), количество возвращенных обновлений равно 1
8 Выйти из цикла и вернуть успешное обновление

5.1.2 Длительное время цикла и высокие накладные расходы

Spin CAS (то есть при неудаче будет выполняться в цикле до тех пор, пока не получится), при длительной неудаче принесет очень большие накладные расходы на выполнение CPU. Личная идея состоит в том, чтобы добавить количество попыток в бесконечный цикл и вернуть отказ, если количество попыток не увенчалось успехом. Не уверен, что есть какие-либо проблемы, добро пожаловать, чтобы указать.

5.1.3 Атомарные операции, которые могут гарантировать только одну общую переменную

CAS действителен только для одной общей переменной и не действует, когда операция охватывает несколько общих переменных. Но, начиная с JDK 1.5, предоставляется класс AtomicReference для обеспечения атомарности между ссылочными объектами, и вы можете поместить несколько переменных в один объект для операций CAS. Таким образом, мы можем использовать блокировки или использовать класс AtomicReference для объединения нескольких общих переменных в одну общую переменную для работы.

6. Недостатки пессимистической блокировки

6.1 synchronized

  • Высвобождается несколько блокировок, и блокировки снимаются только тогда, когда нормальное выполнение программы завершается и выдается исключение;
  • Попытка получить блокировку не может установить тайм-аут;
  • Поток, пытающийся получить блокировку, не может быть прерван;
  • Невозможно узнать, была ли блокировка успешно получена;

6.2 ReentrantLock

  • Вам нужно использовать импорт для импорта соответствующего класса;
  • Не забудьте снять блокировку в модуле finally, это выглядит хуже, чем синхронизировано;
  • Synchronized можно поместить в определение метода, а reentrantlock можно поместить только в блок.Для сравнения, synchronized может уменьшить вложенность;

конец

Если вы считаете, что моя статья полезна для вас, обратите внимание на мой публичный аккаунт в WeChat: «счастливый и болезненный программист» (без рекламы, просто обмен оригинальными статьями, инструменты с использованием pj, различные ресурсы для обучения Java, с нетерпением жду прогресса с тобой)