Для более захватывающего оригинального контента, пожалуйста, обратите внимание на:JavaInterview, добро пожаловать, звезда, поддержите и поощрите следующих авторов, большое спасибо.
Классификация замков
Оптимистическая блокировка и пессимистическая блокировка
Макроклассификация замковоптимистическая блокировкаа такжепессимистический замок. Оптимистичные блокировки и пессимистические блокировки не относятся к конкретной блокировке (в Java нет имени реализации для этой конкретной блокировки, просто называется оптимистическая блокировка или пессимистическая блокировка), но две разные стратегии в параллельных ситуациях.
Optimistic Lock очень оптимистичен: каждый раз, когда вы идете за данными, вы думаете, что другие не изменят их. Так что не закроется. Но если вы хотите обновить данные, будет вПеред обновлением проверьте, не изменили ли другие эти данные в период от чтения до обновления.. Если он был изменен, прочтите его еще раз, попробуйте обновить еще раз и повторите приведенное выше. шагов до тех пор, пока обновление не завершится успешно (конечно, потоку, в котором не удалось выполнить обновление, также разрешено прервать операцию обновления).
Pessimistic Lock очень пессимистичен: каждый раз, когда вы идете за данными, вы думаете, что другие изменят их. Поэтому он блокируется каждый раз, когда берутся данные. Таким образом, когда другие берут данные, они будут заблокированы до тех пор, пока пессимистическая блокировка не будет освобождена, и поток, который хочет получить данные, снова получит блокировку, а затем получит данные.
Пессимистичные блокировки блокируют транзакции, оптимистичные блокировки откатываются и повторяются., у них у всех есть преимущества и недостатки, нет хороших или плохих, только разница между адаптацией к сцене. Например: оптимистическая блокировка подходит для записи В редких случаях, то есть в сценариях, где конфликты возникают редко, это экономит накладные расходы на блокировки и увеличивает общую пропускную способность системы. Но если случаются частые конфликты, верхний Приложение будет продолжать повторять попытки, что снижает производительность, поэтому для этого сценария больше подходит пессимистичная блокировка. Суммировать:Оптимистичные блокировки подходят для сценариев с меньшим количеством операций записи и меньшим количеством конфликтов, а пессимистичные блокировки подходят для сценариев с большим количеством операций записи и большим количеством конфликтов..
Оптимистичная фиксирующая база --- CAS
При реализации оптимистической блокировки мы должны понимать концепцию: CAS.
Что такое КАС? Сравнить и поменять местами, т.е.сравнить и заменить,илиСравните и установите.
-
Сравнение: прочитайте значение A, прежде чем обновлять его до B, проверьте, является ли исходное значение A (не измененным другими потоками,Игнорируйте проблему ABA здесь).
-
Заменить: если да, обновить A до B, конец. Если нет, он не будет обновлен.
Вышеупомянутые два шага являются атомарными операциями, которые можно понимать как мгновенное завершение, которое является одношаговой операцией в глазах ЦП.
С помощью CAS можно реализовать оптимистическую блокировку:
public class OptimisticLockSample{
public void test(){
int data = 123; // 共享数据
// 更新数据的线程会进行如下操作
for (;;) {
int oldData = data;
int newData = doSomething(oldData);
// 下面是模拟 CAS 更新操作,尝试更新 data 的值
if (data == oldData) { // compare
data = newData; // swap
break; // finish
} else {
// 什么都不做,循环重试
}
}
}
/**
*
* 很明显,test() 里面的代码根本不是原子性的,只是展示了下 CAS 的流程。
* 因为真正的 CAS 利用了 CPU 指令。
*
* */
}
В Java CAS также реализуется с помощью нативных методов.
public final class Unsafe {
...
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
...
}
Выше написана простая и интуитивно понятная реализация оптимистической блокировки (точнее, это должен быть процесс оптимистической блокировки), которая позволяет нескольким потокам читать одновременно (поскольку операции блокировки вообще нет), если данные обновлено, Один и только один поток может успешно обновить данные, в результате чего другие потоки откатываются и повторяют попытку. CAS использует инструкции ЦП, чтобы обеспечить атомарность с аппаратного уровня для достижения эффекта блокировки.
Как видно из всего процесса оптимистической блокировки, нетзамока такжеразблокироватьоперация, поэтому оптимистическая стратегия блокировки также называетсябезблокировочное программирование. Другими словами, оптимистическая блокировка на самом деле не является «замком». Это просто алгоритм CAS с повторением цикла.
блокировка спина
синхронизированный и заблокированный интерфейс
Есть два способа реализовать блокировку в Java: один — использовать ключевое слово synchronized, а другой — использовать класс реализации интерфейса Lock.
Видел в статье хорошее сравнение, очень наглядное, ключевое слово synchronized вродеавтоматическая коробка передач, может удовлетворить все потребности вождения. Но если вы хотите выполнять более продвинутые операции, такие как игра в дрифт или всевозможные продвинутые трюки, то вам нужномеханическая коробка передач, который является классом реализации интерфейса Lock.
И синхронизация стала очень эффективной после различных оптимизаций в каждой версии Java. Его просто не так удобно использовать, как класс реализации интерфейса Lock.
Синхронизированный процесс обновления блокировки является ядром его оптимизации:Блокировка смещения -> Легкий замок -> тяжелый замок
class Test{
private static final Object object = new Object();
public void test(){
synchronized(object) {
// do something
}
}
}
При использовании ключевого слова synchronized для блокировки блока кода объект блокировки (то есть объект в приведенном выше коде) не блокируется.тяжелый замок, но склонен к блокировке. Смещенная блокировка буквально означает блокировку, которая «предвзято относится к первому потоку, который ее получает». После того, как поток завершит выполнение синхронизированного блока, иНе будет активно снимать блокировку смещения. Когда синхронизация достигнута во второй раз В блоке кода поток будет определять, является ли поток, удерживающий блокировку, самим собой (идентификатор потока, удерживающего блокировку, хранится в заголовке объекта), и если это так, он будет выполняться нормально.Так как он не был выпущен ранее, Здесь не нужно повторно запирать, если поток использует блокировку от начала до конца, очевидно, что дополнительные накладные расходы в пользу блокировки почти отсутствуют, а производительность чрезвычайно высока.
После присоединения второго потокаблокировка конфликта, смещенная блокировка преобразуется вЛегкий замок(блокировка спина). Конфликт за блокировку: если несколько потоков по очереди получают блокировку, но каждый раз Все идет хорошо, блокировки не происходит, и нет конфликта блокировок. Только когда поток получает блокировку и обнаруживает, что блокировка занята и нужно дождаться ее освобождения, это означает, что произошла конкуренция за блокировку.
Продолжить соревнование замков на облегченном состоянии блокировки и потоке, который не захватил блокировкувращениеОперация, то есть непрерывная оценка возможности получения блокировки в цикле. Операция получения блокировки осуществляется через операцию CAS. Чтобы изменить бит флага блокировки в заголовке объекта. ПервыйсравниватьЯвляется ли текущий флаг блокировкиосвобожденсостояние, если да, установите его взапираниесостояние, сравнение и установка являются атомарными операциями, это Это гарантируется на уровне JVM. Даже если текущий поток удерживает блокировку, этот поток изменяет информацию о текущем держателе блокировки на себя.
Если поток, у которого мы получаем блокировку, будет работать долго, например, он будет выполнять сложные вычисления, передавать по сети большой объем данных и т. д., то другие потоки, ожидающие блокировки, войдут в длительную операцию спина. . Процесс очень ресурсоемкий. По сути, в это время это эквивалентно тому, что эффективно работает только один поток, а остальные ничего не могут сделать, напрасно потребляя ресурсы процессора.Это явление называетсязанят ожиданием (занят-ожидание). Итак, если несколько потоков используютэксклюзивный замок, но конкуренции замков нет, или есть очень небольшая конкуренция замков, тогда синхронизированный легкий Блокировка уровня, позволяющая кратковременно быть занятой и так далее. Это альтернативная идея,Короткое время ожидания в обмен на накладные расходы на переключение потоков между пользовательским режимом и режимом ядра..
Очевидно, что существует ограничение на занятое ожидание (в JVM есть счетчик для записи количества спинов, что позволяет по умолчанию 10 циклов, которые можно пройти черезИзменения параметров виртуальной машины). Если конкуренция за блокировку является серьезной, Поток, достигший определенного максимального количества вращений, обновит облегченную блокировку до тяжелой блокировки (флаг блокировки по-прежнему изменяется через CAS, но идентификатор потока, удерживающего блокировку, не изменяется). Когда последующие потоки пытаются получить При блокировке, если занятая блокировка оказывается тяжеловесной, она напрямую приостанавливается (вместо упомянутого выше занятого ожидания, т. . До JDK1.6 синхронизировано Непосредственно добавляя тяжелые блокировки, очевидно, что после ряда оптимизаций производительность значительно улучшилась.
В JVM синхронизированные блокировки могут быть обновлены только постепенно в следующем порядке: предвзятые блокировки, облегченные блокировки и тяжелые блокировки (также называемые такими блокировками).блокировка раздуванияпроцесс), понижение версии не допускается.
Реентерабельные блокировки (рекурсивные блокировки)
Повторно используемые блокировки буквально означают «блокировки, которые могут быть повторно введены», т.е.Позволяет одному и тому же потоку получать одну и ту же блокировку несколько раз. Например, если в рекурсивной функции есть операция блокировки, блокируется ли сама блокировка в рекурсивной функции? Если нет, то блокировка называется реентерабельной блокировкой (по этой причине реентерабельная блокировка также называетсярекурсивная блокировка).
Блокировки, называемые реентерабельными в Java, являются реентерабельными блокировками.Все готовые классы реализации блокировки, предоставляемые JDK, включая синхронизированные блокировки ключевых слов, являются реентерабельными.. Если вам действительно нужны нереентерабельные блокировки, вам нужно реализовать их самостоятельно, зайдите в Интернет, чтобы найти их, их много, и реализовать их очень просто.
Если это не реентерабельная блокировка, то она вызовет взаимоблокировку в рекурсивной функции, поэтому блокировки в Java в основном являются реентерабельными блокировками, а значение неповторяющихся блокировок не очень велико.Примечание. Друзья, которые думают о необходимости сценариев блокировки без повторного входа, могут оставить сообщение для совместного обсуждения..
На следующем рисунке показаны соответствующие классы реализации Lock:
Честные и нечестные замки
Если несколько потоков применяются длячестный замок, тогда, когда поток, который получает блокировку, снимает блокировку, тот, кто применяет ее первым, получает ее первым, что справедливо. еслинесправедливый замок, поток, который применяется позже, может получить блокировку первым, да Будет ли он получен случайным образом или другими способами, зависит от алгоритма реализации.
Для класса ReentrantLock конструктор может бытьУказывает, является ли блокировка справедливой блокировкой, по умолчанию используется нечестная блокировка.. Поскольку в большинстве случаев пропускная способность недобросовестных блокировок больше, чем пропускная способность честных блокировок, Если нет особых требований, предпочтительно использование недобросовестных замков.
Для синхронизированной блокировки это может быть только несправедливая блокировка, и нет никакого способа сделать ее честной блокировкой. Это также ReentrantLock относительно синхронизированной блокировки. Преимущества, большая гибкость.
Вот код конструктора ReentrantLock:
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock внутренне реализует два внутренних класса FairSync и NonfairSync для реализации справедливых и нечестных блокировок. Конкретный анализ исходного кода будет дан в следующей главе, так что следите за обновлениями. Проект приветствуетсяforkа такжеstar.
прерываемый замок
Буквально «можетОтвечать на прерываниязамок".
Во-первых, нам нужно понять, что такоепрерывать. Java не предоставляет никакого метода, который может напрямую прерывать поток, толькомеханизм прерывания. так почемумеханизм прерыванияШерстяная ткань? Поток A отправляет запрос «пожалуйста, прекратите выполнение» потоку B, который должен вызвать метод Thread.interrupt() (конечно, сам поток B также может отправить запрос на прерывание самому себе, То есть Thread.currentThread().interrupt()), но поток B не прекращает работу сразу, а выбирает реагировать на прерывание по-своему в соответствующий момент времени, или вы можете Просто игнорируйте это прерывание. То есть JavaПрерывания не могут напрямую завершать поток, просто установите состояние в состояние ответа на прерывание, а поток, который необходимо прервать, решает, как с ним поступить. это как Во время учебы учитель просит студентов проверить свою домашнюю работу на вечерней самоподготовке, но будут ли студенты проверять домашнюю работу и как проверять домашнюю работу, полностью зависит от самих студентов.
Вернемся к анализу блокировок. Если поток A удерживает блокировку, поток B ожидает получения блокировки. Поскольку поток A удерживает блокировку слишком долго, поток B не хочет больше ждать, мы можем позволить потоку B прерваться. Прервите B самостоятельно или в другом потоке, этоМожет быть заперт в середине.
В Java синхронизированная блокировкабесперебойный замок, а классы реализации Lockпрерываемый замок. Видно, что блокировка Lock, реализованная самим JDK, более Гибкость, то есть после синхронизированных блокировок зачем еще нужно реализовывать какие-то классы реализации Lock.
Соответствующие определения интерфейса блокировки:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Где lockInterruptably — получить прерываемую блокировку.
общий замок
Буквально несколько потоков могут совместно использовать блокировку. Как правило, общие блокировки используются при чтении данных. Например, мы можем позволить 10 потокам одновременно читать общие данные. Можно установить общую блокировку с 10 учетными данными.
В Java также существуют специальные классы реализации разделяемой блокировки, такие как Semaphore. Анализ исходного кода этого класса будет проанализирован в последующих главах.Обратите внимание на этот проект.Добро пожаловатьforkа такжеstar.
Мьютекс
Буквально означает взаимоисключающие блокировки между потоками, что означает, что блокировка может принадлежать только одному потоку.
В Java ReentrantLock и синхронизированная блокировка являются мьютексными блокировками.
Блокировка чтения-записи
Блокировка чтения-записи на самом деле представляет собой пару блокировок: блокировку чтения (общую блокировку) и блокировку записи (блокировку взаимного исключения, монопольную блокировку).
В Java интерфейс ReadWriteLock определяет только два метода: один возвращает блокировку чтения, а другой возвращает блокировку записи.
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
Как упоминалось ранее в статье [стратегия оптимистической блокировки] (#Основа оптимистической блокировки --- CAS), все потоки могут читать в любое время и только перед записью судить, было ли значение изменено.
Блокировки чтения-записи фактически делают то же самое, но с немного другой стратегией. Во многих случаях поток знает, что после того, как он прочитает данные, он намеревается их изменить. Так почему бы не сделать это ясно при блокировке Что насчет этого? Если я прочитаю значение для его обновления (что означает SQL для обновления), то добавлю его непосредственно при блокировкеблокировка записи, пока я держу блокировку записи, другие потоки Будь то чтение или запись, вам нужно подождать; если чтение данных только для внешнего отображения, то явно добавьте блокировку при блокировкеблокировка чтения, если другим потокам также необходимо добавить блокировки чтения, им не нужно ждать. Получить напрямую (увеличить счетчик блокировки чтения на 1).
Хотя блокировки чтения-записи немного напоминают оптимистичные блокировки,Блокировки чтения-записи — это пессимистичные стратегии блокировки.. Поскольку блокировка чтения-записи неПеред обновлениемСудя по тому, было ли изменено значение, но вПеред блокировкойПринимать решение Следует ли использовать блокировку чтения или блокировку записи. Оптимистическая блокировка относится конкретно к программированию без блокировки.
Единственным классом реализации интерфейса ReadWriteLock, предоставляемым JDK, является ReentrantReadWriteLock. Из названия видно, что блокировка обеспечивает блокировку чтения-записи, а также Ревходная блокировка.
Суммировать
Различные блокировки, используемые в Java, в основномпессимистический замок, так есть ли оптимистическая блокировка в Java? Результат да, это следующий java.util.concurrent.atomic Атомарные классы реализованы через оптимистическую блокировку. следующим образом:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Из приведенного выше исходного кода видно, что CAS продолжает работать в цикле до тех пор, пока не добьется успеха.
Введение параметра
-XX:-UseBiasedLocking=false 关闭偏向锁
JDK1.6
-XX:+UseSpinning 开启自旋锁
-XX:PreBlockSpin=10 设置自旋次数
JDK1.7 之后 去掉此参数,由 JVM 控制