Три интервью подряд
Интервьюер: Вы разблокировали его?
Сяо Мин: Я понимаю, я часто использовал его.
Интервьюер: Расскажите мне, в чем разница между синхронизацией и блокировкой?
Сяо Мин: синхронизированный — это блокировка с повторным входом. Поскольку замок — это интерфейс, повторный вход зависит от реализации. Синхронизированный не поддерживает прерывание, а замок — может. . . . . . . . . . . . . . . .
Интервьюер: А есть ли замок, который быстрее этих двух замков?
Сяо Мин: В случае большего чтения и меньшего количества записей блокировки чтения-записи более эффективны, чем они.
Интервьюер: Существуют ли более быстрые блокировки, чем блокировки чтения-записи?
Сяо Мин: . . . . . . . . . .
Я полагаюсь на, спросите так глубоко? Сяо Мин был обманут в то время, потому что наиболее часто используемый проект в его проекте синхронизирован, а блокировка чтения-записи используется редко, потому что многопоточность редко используется.Это интервью дало ему понять важность многопоточности.
Что такое блокировка чтения-записи
Блокировка чтения-записи: несколько потоков могут читать одновременно, но только один поток может писать. Когда поток получает блокировку записи, другие операции записи и чтения будут заблокированы. Блокировки чтения и записи также блокируются. взаимоисключающие, поэтому при чтении Когда запись не разрешена, как реализовать блокировку чтения-записи?
Блокировка чтения-записи намного быстрее, чем традиционная синхронизация. Причина в том, что блокировка чтения-записи поддерживает параллелизм чтения, а синхронизация требует сериализации всех операций. Например, мне нужно запросить основную информацию о пользователе, что очень мало изменений, поэтому мы будем хранить эту часть информации в кеше.Наша операция запроса:
Согласно приведенной выше блок-схеме, если используется синхронизация, кэш запросов будет заблокирован, но при использовании блокировок чтения-записи кэш запросов является параллельным, а база данных запросов заблокирована.Поэтому производительность блокировок чтения-записи очевидно лучше, когда больше операций чтения и меньше операций записи. Лучше, чем при синхронизации.
Человеческая цивилизация прогрессирует, 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 по вызову автомобилей)
Учебные материалы по архитектуре расширенной разработки
Исходный код боевых книг
Видео для промежуточной и продвинутой архитектуры, полный набор видео
Как получить упомянутый в статье контент: после лайка + подпискакликните сюдаПолучите это бесплатно
Сборник интервью Дачан
Нравится + Подписатьсякликните сюдаПолучите бесплатные видеоролики об архитектуре, интервью с гидами крупных заводов, обучающие видеоролики по архитектуре в высоком разрешении и актуальные документы онлайн-проекта Ма Дасюн ~