Интервьюер Meituan: Существует ли более быстрая блокировка, чем блокировка чтения-записи?

Архитектура

Три интервью подряд

Интервьюер: Вы разблокировали его?

Сяо Мин: Я понимаю, я часто использовал его.

Интервьюер: Расскажите мне, в чем разница между синхронизацией и блокировкой?

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

Интервьюер: А есть ли замок, который быстрее этих двух замков?

Сяо Мин: В случае большего чтения и меньшего количества записей блокировки чтения-записи более эффективны, чем они.

Интервьюер: Существуют ли более быстрые блокировки, чем блокировки чтения-записи?

Сяо Мин: . . . . . . . . . .

Я полагаюсь на, спросите так глубоко? Сяо Мин был обманут в то время, потому что наиболее часто используемый проект в его проекте синхронизирован, а блокировка чтения-записи используется редко, потому что многопоточность редко используется.Это интервью дало ему понять важность многопоточности.

Что такое блокировка чтения-записи

Блокировка чтения-записи: несколько потоков могут читать одновременно, но только один поток может писать. Когда поток получает блокировку записи, другие операции записи и чтения будут заблокированы. Блокировки чтения и записи также блокируются. взаимоисключающие, поэтому при чтении Когда запись не разрешена, как реализовать блокировку чтения-записи?

Блокировка чтения-записи намного быстрее, чем традиционная синхронизация. Причина в том, что блокировка чтения-записи поддерживает параллелизм чтения, а синхронизация требует сериализации всех операций. Например, мне нужно запросить основную информацию о пользователе, что очень мало изменений, поэтому мы будем хранить эту часть информации в кеше.Наша операция запроса:

美团面试官:有没有比读写锁更快的锁?


Согласно приведенной выше блок-схеме, если используется синхронизация, кэш запросов будет заблокирован, но при использовании блокировок чтения-записи кэш запросов является параллельным, а база данных запросов заблокирована.Поэтому производительность блокировок чтения-записи очевидно лучше, когда больше операций чтения и меньше операций записи. Лучше, чем при синхронизации.

Человеческая цивилизация прогрессирует, java тоже прогрессирует, а стремление к знаниям тоже возрастает, поэтому мы постоянно думаем над таким вопросом, чтение и запись блокировки чтения-записи взаимоисключающие, поэтому можем ли мы читать И писать поддержку параллелизм?

Родился StampedLock

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

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

Три режима StampedLock

Пессимистическое чтение: аналогично блокировкам чтения-записи, позволяющее нескольким потокам получать пессимистичные блокировки чтения.

Блокировка записи: Подобно блокировке записи блокировки чтения-записи, блокировка записи и пессимистическое чтение являются взаимоисключающими.

Оптимистическое чтение: Механизм без блокировки, аналогичный оптимистическим блокировкам в базах данных, поддерживает блокировку записи, когда блокировка записи не установлена, что отличается от блокировки чтения-записи.

базовая грамматика

Давайте сначала рассмотрим базовый синтаксис пессимистичных блокировок чтения и записи.

// получаем пессимистическое чтение

long stamp = lock.readLock();

try{

String info = mapCache.get(name);

if(null != info){

return info;

}

}finally {

// отключаем пессимистическое чтение

lock.unlock(stamp);

}


//Получить блокировку записи

stamp = lock.writeLock();

try{

//Проверяем, вставлены ли данные в кеш

String info = mapCache.get(name);

if(null != info){

return info;

}

//Здесь нужно получить данные из базы данных

String infoByDb = mapDb.get(name);

//вставляем данные в кеш

mapCache.put(name,infoByDb);

}finally {

// снять блокировку записи

lock.unlock(stamp);

}


Мы видим, что есть небольшая разница между синтаксисом StampedLock и блокировкой чтения-записи ReentrantReadWriteLock,

Возвращаемое значение получения блокировки:

StampedLock: длинный

ReentrantReadWriteLock: блокировка

Как снять блокировку:

StampedLock: разблокировать (штамп), вам нужно передать длинное значение, возвращаемое при получении блокировки.

ReentrantReadWriteLock: unlock(), просто вызовите метод разблокировки напрямую.

Полная демонстрация StampedLock

package com.ymy.test;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.locks.StampedLock;

public class StampedLockTest {

private static final StampedLock lock = new StampedLock();

//данные хранятся в кеше

private static Map<String,String> mapCache = new HashMap<String, String>();

// симулируем данные, хранящиеся в базе данных

private static Map<String,String> mapDb = new HashMap<String, String>();

static {

mapDb.put("zhangsan", "Привет, я Zhangsan");

mapDb.put("sili","Привет, я Ли Си");

}

private static String getInfo(String name){

// получаем пессимистическое чтение

long stamp = lock.readLock();

try{

String info = mapCache.get(name);

if(null != info){

System.out.println("Получить данные в кэше");

return info;

}

}finally {

// отключаем пессимистическое чтение

lock.unlock(stamp);

}

//Получить блокировку записи

stamp = lock.writeLock();

try{

//Проверяем, вставлены ли данные в кеш

String info = mapCache.get(name);

if(null != info){

System.out.println("Получена блокировка записи, подтверждено, что данные были получены в кэше");

return info;

}

//Здесь нужно получить данные из базы данных

String infoByDb = mapDb.get(name);

//Проговариваем данные в кеш

mapCache.put(name,infoByDb);

System.out.println("Данных в кеше нет, данные получены в базе данных");

}finally {

// снять блокировку записи

lock.unlock(stamp);

}

return null;

}

public static void main(String[] args) {

//поток 1

Thread t1 = new Thread(() ->{

getInfo("zhangsan");

});

//поток 2

Thread t2 = new Thread(() ->{

getInfo("zhangsan");

});

// начало потока

t1.start();

t2.start();

//синхронизация потоков

try {

t1.join();

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}


Это способ использования пессимистической блокировки чтения + записи. Достигается эффект такой же, как и у ReentrantReadWriteLock. Давайте проверим это вместе. Я немного изменил код, распечатал журналы выполнения двух потоков, и в то же время Когда вызывающий поток - zhangsan, он спит в течение трех секунд. Цель состоит в том, чтобы увидеть, может ли поток lisi успешно получить блокировку записи. Код выглядит следующим образом:

package com.ymy.test;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.locks.StampedLock;

import java.util.logging.Logger;

public class StampedLockTest {

private static Logger log = Logger.getLogger(StampedLockTest.class.getName());

private static final StampedLock lock = new StampedLock();

//данные хранятся в кеше

private static Map<String,String> mapCache = new HashMap<String, String>();

// симулируем данные, хранящиеся в базе данных

private static Map<String,String> mapDb = new HashMap<String, String>();

static {

mapDb.put("zhangsan", "Привет, я Zhangsan");

mapDb.put("sili","Привет, я Ли Си");

}

private static String getInfo(String name){

// получаем пессимистическое чтение

long stamp = lock.readLock();

log.info("Имя потока: "+Thread.currentThread().getName()+" Получена пессимистическая блокировка чтения" +" Имя пользователя: "+name);

try{

if("zhangsan".equals(name)){

log.info("Имя потока: "+Thread.currentThread().getName()+" Sleeping" +" Имя пользователя: "+name);

Thread.sleep(3000);

log.info("Имя потока: "+Thread.currentThread().getName()+" Сон окончен" +" Имя пользователя: "+name);

}

String info = mapCache.get(name);

if(null != info){

log.info("Получить данные в кэше");

return info;

}

} catch (InterruptedException e) {

log.info("Имя потока: "+Thread.currentThread().getName()+" снята пессимистичная блокировка чтения");

e.printStackTrace();

} finally {

// отключаем пессимистическое чтение

lock.unlock(stamp);

}

//Получить блокировку записи

stamp = lock.writeLock();

log.info("Имя потока: "+Thread.currentThread().getName()+" Получена блокировка записи" +" Имя пользователя: "+name);

try{

//Проверяем, вставлены ли данные в кеш

String info = mapCache.get(name);

if(null != info){

log.info("Получена блокировка записи, подтверждено, что данные были получены в кеше");

return info;

}

//Здесь нужно получить данные из базы данных

String infoByDb = mapDb.get(name);

//Проговариваем данные в кеш

mapCache.put(name,infoByDb);

log.info("Данных в кеше нет, данные получены из базы данных");

}finally {

// снять блокировку записи

log.info("Имя потока: "+Thread.currentThread().getName()+" Блокировка записи снята" +" Имя пользователя: "+name);

lock.unlock(stamp);

}

return null;

}

public static void main(String[] args) {

//поток 1

Thread t1 = new Thread(() ->{

getInfo("zhangsan");

});

//поток 2

Thread t2 = new Thread(() ->{

getInfo("lisi");

});

// начало потока

t1.start();

t2.start();

//синхронизация потоков

try {

t1.join();

t2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}


Если поток Ли Си получает блокировку записи во время фазы сна потока zhansan, это означает, что пессимистичные блокировки чтения и записи не являются взаимоисключающими. В противном случае они являются взаимоисключающими. См. результаты выполнения кода:

29 марта 2020 г., 11:30:58 com.ymy.test.StampedLockTest getInfo

ИНФОРМАЦИЯ: Название потока: Thread-2 приобрел пессимистическую блокировку чтения, имя пользователя: lisi

29 марта 2020 г., 11:30:58 com.ymy.test.StampedLockTest getInfo

Информация: Название потока: Thread-1 получил пессимистическую блокировку чтения Имя пользователя: zhangsan

29 марта 2020 г., 11:30:58 com.ymy.test.StampedLockTest getInfo

Информация: Название темы: Thread-1 спит Имя пользователя: zhangsan

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

Информация: Название темы: Спящий режим Thread-1 завершен Имя пользователя: zhangsan

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

Информация: Название потока: Thread-1 получил блокировку записи Имя пользователя: zhangsan

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

Сообщение: В кеше нет данных, данные извлекаются из базы данных

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

Информация: Название темы: Thread-1 снял блокировку записи Имя пользователя: zhangsan

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

ИНФОРМАЦИЯ: Имя потока: Thread-2 получил блокировку записи Имя пользователя: lisi

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

Сообщение: В кеше нет данных, данные извлекаются из базы данных

29 марта 2020 г., 11:31:01 com.ymy.test.StampedLockTest getInfo

ИНФОРМАЦИЯ: Название потока: Thread-2 снял блокировку записи Имя пользователя: lisi


Давайте внимательно посмотрим на время вывода журнала печати: в 11:30:58 и lisi, и zhangsan получили пессимистическую блокировку чтения, и zhangsan начал спать, затем в 11:31:01 сон закончился, и zhangsan приобрел блокировку записи, поэтому пессимистическое чтение совпадает с тем, что блокировки записи должны быть взаимоисключающими, поэтому разве эта эффективность не такая же, как блокировки чтения-записи? Почему говорят, что это быстрее, чем блокировка чтения-записи? Разве это не противоречие?

Гость, не волнуйтесь, помните, что прекрасное всегда в конце, мы использовали только два режима специальной блокировки StampedLock, а другой еще не появился.Давайте посмотрим на оптимистичное чтение.

Оптимистическое чтение для повышения производительности StampedLock

Оптимистическое чтение не является блокировкой, поэтому не связывайте его с пессимистическим чтением, это механизм без блокировки, который эквивалентен атомарной операции java, поэтому теоретически производительность будет выше, чем блокировка чтения-записи. (ReentrantReadWriteLock).Но не совсем.

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

Давайте посмотрим на реализацию оптимистического чтения:

package com.ymy.test;

import java.util.concurrent.locks.StampedLock;

public class NumSumTest {

private static final StampedLock lock = new StampedLock();

private static int num1 = 1;

private static int num2 = 1;

/**

* Измените значение переменной-члена, +1

*

* @return

*/

private static int sum() {

System.out.println("Выполнен метод суммирования");

// получаем оптимистичное чтение

long stamp = lock.tryOptimisticRead();

int cnum1 = num1;

int cnum2 = num2;

System.out.println("Получить значение переменной-члена, cnum1: " + cnum1 + " cnum2: " + cnum2);

try {

//Сон на 3 секунды, цель состоит в том, чтобы позволить другим потокам изменить значение переменных-членов.

Thread.sleep(3000);

} catch (InterruptedException e) {

e.printStackTrace();

}

//Определяем, есть ли операция записи во время выполнения true: не существует false: существует

if (!lock.validate(stamp)) {

System.out.println("Идет операция записи!");

// есть блокировка записи

//Обновить блокировку пессимистического чтения

stamp = lock.readLock();

try {

System.out.println("Обновить блокировку пессимистического чтения");

cnum1 = num1;

cnum2 = num2;

System.out.println("Повторно получить значение переменной-члена =========== cnum1="+cnum1 +" cnum2="+cnum2);

} finally {

// снять пессимистическую блокировку чтения

lock.unlock(stamp);

}

}

return cnum1 + cnum2;

}

//Используем блокировку записи для изменения значения переменной-члена

private static void updateNum() {

long stamp = lock.writeLock();

try {

num1 = 2;

num2 = 2;

} finally {

lock.unlock(stamp);

}

}

public static void main(String[] args) throws InterruptedException {

Thread t1 = new Thread(() -> {

int sum = sum();

System.out.println("Результат суммы: " + сумма);

});

t1.start();

//Сон на 1 секунду, цель состоит в том, чтобы позволить потоку t1 выполняться до тех пор, пока переменная-член не будет получена

Thread.sleep(1000);

updateNum();

t1.join();

System.out.println("Выполнено");

}

}

Объясните код, определите две переменные-члены и используйте поток t1 для вычисления суммы двух переменных-членов.Чтобы отразить эффект оптимистичного чтения, я сплю в sum() в течение 3 секунд, цель состоит в том, чтобы позволить основной поток Чтобы изменить значение переменной-члена, сон в основной функции должен позволить потоку t1 точно выполниться до этапа чтения переменной-члена.

Посмотрим на результат выполнения:

Выполняется метод суммирования

Полученное значение переменной-члена, cnum1: 1 cnum2: 1

Есть операция записи!

Модернизация пессимистичных блокировок чтения

Повторно получено значение переменной-члена =========== cnum1=2 cnum2=2

Суммарный результат: 4

Законченный

Мы обнаружили, что t1 сначала прочитал значения двух переменных-членов, а затем обнаружил, что была операция записи, поскольку функция main использовала блокировку записи для изменения значений двух переменных-членов. он был обновлен до пессимистического чтения, и снова были получены переменные-члены Значение , а затем вычислить сумму двух значений, зачем обновлять пессимистическую блокировку чтения? Поскольку в начале статьи было сказано, что пессимистическая блокировка чтения и блокировка записи являются взаимоисключающими, а пессимистическая блокировка чтения параллельна перед пессимистической блокировкой чтения, поэтому оптимистическая блокировка чтения обновляется до пессимистической блокировки чтения, а затем переменные-члены получаются снова, что может гарантировать, что данные в текущей блокировке пессимистического чтения являются потокобезопасными.

Вы понимаете сценарии применения оптимистического чтения?

Оптимистическое чтение не является патентом StampedLock.Оптимистическое чтение используется во многих местах, таких как оптимистическая блокировка и пессимистическая блокировка баз данных, а также атомарные инструменты инструментов параллелизма Java.

Пессимистичные блокировки базы данных и оптимистичные блокировки могут относиться к: mysql: пессимистические блокировки и оптимистичные блокировки

Java Concurrency Tools Atomic Class Reference: Java Concurrency Programming: CAS (Compare and Swap)

Примечания по использованию StampedLock

1. StampedLock является подклассом ReadWriteLock, а ReentrantReadWriteLock также является подклассом ReadWriteLock, вы нашли разницу между ними? Из названия видно, что StampedLock не поддерживает повторные блокировки.

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

3. Пессимистичные блокировки чтения и записи StampedLock не поддерживают условные переменные.

4. Никогда не прерывайте заблокированную пессимистическую блокировку чтения или записи. Если вы вызываете прерывание () заблокированного потока, это приведет к взлету ЦП. Если вы хотите, чтобы StampedLock поддерживал операции прерывания, используйте readLockInterruptently (пессимистическая блокировка чтения) и writeLockInterruptably (блокировка записи).

Суммировать

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

Оптимистическое чтение поддерживает одновременную блокировку записи, в то время как пессимистическое чтение и блокировка записи являются взаимоисключающими, поэтому при использовании мы можем сначала использовать оптимистическое чтение. Затем оцените, есть ли блокировка записи.Если есть, вы можете обновить пессимистическую блокировку чтения.Из-за взаимного исключения пессимистической блокировки чтения и блокировки записи он может гарантировать безопасность потока.Если Сяомин обычно читает мой вести блог чаще , возможно, эта проблема не ставит его в тупик.

Спасибо всем красивым мальчикам/девочкам за просмотр! После прочтения не забудьте поставить лайк своему брату и подписаться~

Наконец

Друзья-программисты, которым нужны избранные вопросы и ответы для интервью, учебные материалы Java-архитектора и обучающие видео, могут поставить лайк + подписатьсякликните сюдаВы можете получить его бесплатно, не забудьте поставить лайк и подписаться~

(Благополучие в конце статьи: Белая книга о реальной борьбе с онлайн-проектом Ma Soldier по вызову автомобилей)

Учебные материалы по архитектуре расширенной разработки

Исходный код боевых книг

华为社招面试攻略:Redis中是如何实现分布式锁的?

Видео для промежуточной и продвинутой архитектуры, полный набор видео

华为社招面试攻略:Redis中是如何实现分布式锁的?

Как получить упомянутый в статье контент: после лайка + подпискакликните сюдаПолучите это бесплатно

Сборник интервью Дачан

华为社招面试攻略:Redis中是如何实现分布式锁的?


Нравится + Подписатьсякликните сюдаПолучите бесплатные видеоролики об архитектуре, интервью с гидами крупных заводов, обучающие видеоролики по архитектуре в высоком разрешении и актуальные документы онлайн-проекта Ма Дасюн ~

Онлайн-проект автозаказа

华为社招面试攻略:Redis中是如何实现分布式锁的?