Метка: статья в публичном аккаунте "Мы все маленькие лягушки"
Если поток выполняется от начала до конца без взаимодействия с другими потоками, проблем с безопасностью не возникает. Тем не менее, сотрудничество все больше становится общей тенденцией социального развития.После того, как большая задача разделена на несколько небольших задач, каждая маленькая задача также может нуждаться в сотрудничестве друг с другом, чтобы в конечном итоге выполнить большую задачу. Таким образом, каждый поток может взаимодействовать друг с другом во время выполнения.通信, так называемый通信Это относится к обмену некоторыми данными или отправке некоторых управляющих инструкций. Например, поток отправляет команду возобновления выполнения другому потоку, который приостановил выполнение. Ниже приводится подробный обзор доступных методов связи.
изменчивый и синхронизированный
Переменные общие переменные являются естественными средствами связи, то есть, если поток хочет связаться с другим потоком, он может изменить переменную, совместно используемую несколькими потоками, а другой поток может получить содержимое сообщения, прочитав общую переменную. .
Благодаря атомарным операциям, видимости памяти и переупорядочению инструкций, Java обеспечиваетvolatileиsynchronizedСинхронизация означает обеспечение корректности содержания связи.Если таких средств синхронизации нет, запись одного потока не может быть сразу же замечена другим потоком, то такой вид связи ненадежен~
wait/notifyмеханизм
предыстория истории
Я не знаю, чтобы тот, кого убили боги, засунул пластиковую бутылку в яму нашего школьного туалета, из-за чего коридор разлился, как Желтая река, и появился отвратительный запах. Что еще трагичнее, так это то, что на все здание всего один туалет, и что еще трагичнее, что в этом туалете только одна яма! ! ! ! ! Что ж, опишем этот туалет на java:
public class Washroom {
private volatile boolean isAvailable = false; //表示厕所是否是可用的状态
private Object lock = new Object(); //厕所门的锁
public boolean isAvailable() {
return isAvailable;
}
public void setAvailable(boolean available) {
this.isAvailable = available;
}
public Object getLock() {
return lock;
}
}
isAvailableПоле показывает, доступен ли туалет, потому что туалет поврежден, по умолчаниюfalseиз,lockПоле представляет собой замок для этой двери туалета. должен быть в курсеisAvailableполеvolatileМодификация, то есть, если поток изменяет свое значение, это может быть немедленно видно другим потокам~
В связи с драгоценным ресурсом туалетов мудрые школьные руководители сразу же сформулировали задачу восстановления:
public class RepairTask implements Runnable {
private Washroom washroom;
public RepairTask(Washroom washroom) {
this.washroom = washroom;
}
@Override
public void run() {
synchronized (washroom.getLock()) {
System.out.println("维修工 获取了厕所的锁");
System.out.println("厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。");
try {
Thread.sleep(5000L); //用线程sleep表示维修的过程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
washroom.setAvailable(true); //维修结束把厕所置为可用状态
System.out.println("维修工把厕所修好了,准备释放锁了");
}
}
}
Содержание этого плана технического обслуживания заключается в том, что когда обслуживающий персонал входит в туалет, он сначала запирает дверь, а затем начинает техническое обслуживание.WashroomизisAvailableполе установлено наtrue, чтобы указать, что туалет доступен.
В то же время перед дверью туалета кружит группа парней, озабоченных, как муравьи на горячей кастрюле, которые хотят быть богами и лошадьми без моего прямого высказывания😏😏:
public class ShitTask implements Runnable {
private Washroom washroom;
private String name;
public ShitTask(Washroom washroom, String name) {
this.washroom = washroom;
this.name = name;
}
@Override
public void run() {
synchronized (washroom.getLock()) {
System.out.println(name + " 获取了厕所的锁");
while (!washroom.isAvailable()) {
// 一直等
}
System.out.println(name + " 上完了厕所");
}
}
}
этоShitTaskОписывает процесс посещения туалета. Сначала получите замок туалета, а затем оцените, доступен ли туалет. Если он недоступен, он будет постоянно оценивать, доступен ли туалет в бесконечном цикле, пока туалет не будет доступен. , а затем снимите блокировку после посещения туалета.
Тогда давайте посмотрим, что происходит в реальном мире:
public class Test {
public static void main(String[] args) {
Washroom washroom = new Washroom();
new Thread(new RepairTask(washroom), "REPAIR-THREAD").start();
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(new ShitTask(washroom, "狗哥"), "BROTHER-DOG-THREAD").start();
new Thread(new ShitTask(washroom, "猫爷"), "GRANDPA-CAT-THREAD").start();
new Thread(new ShitTask(washroom, "王尼妹"), "WANG-NI-MEI-THREAD").start();
}
}
Сначала школа позволила обслуживающему персоналу войти в туалет для ремонта, а затем армия туалетов, в том числе Братец Пёс, Кот Е и Ван Нимей, начали свое путешествие по туалету Давайте посмотрим на результаты казни:
维修工 获取了厕所的锁
厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。
维修工把厕所修好了,准备释放锁了
王尼妹 获取了厕所的锁
王尼妹 上完了厕所
猫爷 获取了厕所的锁
猫爷 上完了厕所
狗哥 获取了厕所的锁
狗哥 上完了厕所
Вроде с конем проблем нет, но оглянувшись на код, я обнаружил два особо неудобных места:
-
Открыть в основной теме
REPAIR-THREADПосле поток вы должны позвонитьsleepМетод ожидает некоторое время, прежде чем позволить туалетному потоку запуститься.если
REPAIR-THREADЕсли нить и другие нити туалета открыты вместе, люди, которые ходят в туалет, такие как Брат Пёс, могут сначала получить замок туалета, а затем обслуживающий работник вообще не сможет даже войти в туалет. Но реальная ситуация может быть такой: сначала брат Гоу пошел в туалет, а потом пришел ремонтник. Однако лечение брата Гоу должно заключаться не в том, чтобы все время оставаться в туалете, а в том, чтобы сначала выйти и подождать, когда обслуживающий персонал скажет, что он починил его, прежде чем войти. Так что немного неловко... -
Должно быть постоянное суждение, когда пользователь туалета получает замок на туалете.
WashroomизisAvailableПолеtrue.Если человек заходит в туалет и обнаруживает, что туалет по-прежнему недоступен, то он должен где-то отдохнуть, а когда ремонтник починит туалет, просто позвать человека, ожидающего туалета, не надо я все время ходил проверять, был ли туалет отремонтировано.
Подводя итог, этоПосле того, как поток получает блокировку, если указанные условия не выполняются, он должен активно отказаться от блокировки, а затем перейти в специальную область ожидания.等待, пока поток не выполнит указанное условие, а затем通知Пусть потоки, ожидающие выполнения этого условия, пусть продолжают выполнение.
Если вы думаете, что приведенное выше предложение сбивает с толку, позвольте мне перевести его для вас: Когда Шангоу получает замок на дверь туалета, если туалет недоступен, он проявляет инициативу, чтобы отказаться от замка, а затем встает в очередь, чтобы ждать в очереди в ожидании туалета, пока обслуживающий работник не отремонтирует туалет и не изменит состояние После того, как он установлен как доступный, обслуживающий персонал уведомляет людей, которым нужно сходить в туалет, и они ходят в туалет в обычном режиме.
конкретное использование
Чтобы реализовать эту идею, java предлагает набор, называемыйwait/notifyМеханизмы. После того, как поток получит блокировку, если он обнаружит, что условие не выполнено, он проявит инициативу отказаться от блокировки, а затем поместит поток в блокировку.等待队列внутри等待Иди, подожди, пока поток не выполнит это условие, затем通知Ожидая, пока потоки в очереди удовлетворят условиям, которых они ждут, они могут продолжать работу!
Что, если разные потоки имеют разные условия ожидания, их нельзя впихнуть все в один и тот же等待队列в? Да, это указано в javaКаждый замок соответствует等待队列, то есть, если поток обнаруживает, что определенное условие не выполняется после получения блокировки, он активно отказывается от блокировки, а затем помещает поток в очередь ожидания, соответствующую полученной блокировке, и завершается другой поток. соответствующее условие.Он должен получить ту же блокировку, когда условие выполнено, и уведомить его о соответствующей блокировке полученной блокировки после выполнения условия.等待队列. Этот процесс означаетБлокировки и очереди ожидания устанавливают отношение один к одному.
каквыдать блокировку и поместить поток в очередь ожидания, связанную с блокировкойи какУведомление о том, что связанное с потоком условие в очереди ожидания выполненоJava уже обеспечила нас. мы знаем,锁На самом деле это просто объект, в классе-предке всех объектовObjectНесколько методов определены в:
public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException
public final void notify();
public final void notifyAll();
| имя метода | инструкция |
|---|---|
wait() |
После того, как поток получает блокировку, вызывается этот метод объекта блокировки, поток освобождает блокировку и помещается в очередь ожидания, связанную с объектом блокировки. |
wait(long timeout) |
иwait()Метод аналогичен, за исключением того, что он ждет указанное количество миллисекунд, и если указанное время превышено, поток автоматически удаляется из очереди ожидания |
wait(long timeout, int nanos) |
То же, что и выше, за исключением того, что степень детализации времени ожидания меньше, то есть указанное количество миллисекунд плюс количество наносекунд. |
notify() |
Уведомляет поток в очереди ожидания, связанный с объектом блокировки, о возврате из метода wait() для продолжения выполнения |
notifyAll() |
Аналогично предыдущему, за исключением того, что все потоки в очереди ожидания уведомляются |
Поняв смысл этих методов, перепишем ихShitTask:
public class ShitTask implements Runnable {
// ... 为节省篇幅,省略相关字段和构造方法
@Override
public void run() {
synchronized (washroom.getLock()) {
System.out.println(name + " 获取了厕所的锁");
while (!washroom.isAvailable()) {
try {
washroom.getLock().wait(); //调用锁对象的wait()方法,让出锁,并把当前线程放到与锁关联的等待队列
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(name + " 上完了厕所");
}
}
}
Смотрите, получается, что мы добавили этот кусок кода в бесконечный цикл, определяющий, доступен ли туалет:
washroom.getLock().wait();
Смысл этого кода в том, чтобы снять блокировку унитаза и поместить текущий поток в очередь ожидания, связанную с блокировкой унитаза.
Затем нам также нужно изменить задачу обслуживания:
public class RepairTask implements Runnable {
// ... 为节省篇幅,省略相关字段和构造方法
@Override
public void run() {
synchronized (washroom.getLock()) {
System.out.println("维修工 获取了厕所的锁");
System.out.println("厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。");
try {
Thread.sleep(5000L); //用线程sleep表示维修的过程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
washroom.setAvailable(true); //维修结束把厕所置为可用状态
washroom.getLock().notifyAll(); //通知所有在与锁对象关联的等待队列里的线程,它们可以继续执行了
System.out.println("维修工把厕所修好了,准备释放锁了");
}
}
}
Как видите, после ремонта мы добавили эту строчку кода:
washroom.getLock().notifyAll();
Этот код указывает, что все потоки в очереди ожидания, связанные с объектом блокировки, будут уведомлены о том, что они могут продолжить выполнение.
При использовании javawait/notifyМеханизм был измененShitTaskиRepairTaskПосле этого восстанавливаем всю реальную сцену:
public class Test {
public static void main(String[] args) {
Washroom washroom = new Washroom();
new Thread(new ShitTask(washroom, "狗哥"), "BROTHER-DOG-THREAD").start();
new Thread(new ShitTask(washroom, "猫爷"), "GRANDPA-CAT-THREAD").start();
new Thread(new ShitTask(washroom, "王尼妹"), "WANG-NI-MEI-THREAD").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(new RepairTask(washroom), "REPAIR-THREAD").start();
}
}
В этой сцене мы можем намеренно позволить тем, кто спешит в туалет, прийти в туалет первым, а обслуживающему персоналу — последним.wait/notifyРезультат выполнения кода механизма:
狗哥 获取了厕所的锁
猫爷 获取了厕所的锁
王尼妹 获取了厕所的锁
维修工 获取了厕所的锁
厕所维修中,维修厕所是一件辛苦活,需要很长时间。。。
维修工把厕所修好了,准备释放锁了
王尼妹 上完了厕所
猫爷 上完了厕所
狗哥 上完了厕所
Из результатов казни видно, что хотя брат Гоу, Е Мао и Ван Нимэй первыми подошли к туалету и получили замок, все они позвонили первыми, потому что туалет был недоступен.wait()Метод отказался от замка, который он получил, а затем спрятался в очереди ожидания, связанной с замком.Только тогда, когда обслуживающий рабочий закончил ремонт туалета и уведомил брата-собаку, хозяина кошек и Ван Нимея, которые находились в ожидании. очереди, и они начали продолжать.Проводить процедуру посещения туалета~
Общий режим
После случая с туалетом выше все должны быть правыwait/notifyИмея общее представление о механизме, давайте резюмируем общий режим работы этого механизма. Сначала взгляните на общий шаблон ожидающих потоков:
- Получите блокировку объекта.
- Если определенное условие не выполняется, вызовите функцию блокировки объекта.
waitметод, который по-прежнему проверяет, выполняется ли условие после получения уведомления. - Продолжайте выполнять код, если условие будет выполнено.
Общий код выглядит следующим образом:
synchronized (对象) {
处理逻辑(可选)
while(条件不满足) {
对象.wait();
}
处理逻辑(可选)
}
Помимо оценки выполнения условий и вызоваwaitКод, отличный от метода, другой логики обработки является необязательным.
увидеть ниже通知Общий шаблон для нитей:
- Получает блокировку объекта.
- Выполните условия.
- Уведомлять ожидающие потоки в очереди ожидания.
synchronized (对象) {
完成条件
对象.notifyAll();、
}
Советы: Не забывайте, что методы синхронизации также используют блокировки.Объектом блокировки метода статической синхронизации является «объект класса» класса, а объектом блокировки метода синхронизации члена является «этот объект». Таким образом, если это специально не подчеркивается, упомянутый ниже синхронизированный блок кода также содержит синхронизированные методы.
понялwait/notifyПосле общего режима нужно быть очень осторожным при его использовании, нужно обратить внимание на следующие аспекты:
-
Должен вызываться в блоке синхронизированного кода
wait,notifyилиnotifyAllметод.У некоторых детской обуви возникнут вопросы, почему
wait/notifyЭти методы механизма должны быть помещены в блок синхронизированного кода для вызова?waitСмысл метода в том, чтобы остановить выполнение текущего потока и поставить текущий поток в очередь ожидания.notifyСмысл метода как раз в том, чтобы убрать поток из очереди ожидания, при чем тут блокировка?Ответ: Потому что
waitМетод работает в ожидающей поток,notifyилиnotifyAllработает в потоке уведомлений. при выполненииwaitПеред методом нужно судить, выполняется ли определенное условие, и если нет, то оно будет выполнено.waitметод, это先检查后执行операция, а не原子性操作Поэтому, если вы не заблокированы, порядок выполнения потоков и нитей уведомлений в многопоточной среде может быть таким:То есть, когда поток ожидания был оценен, он не удовлетворен.
waitметод, в это время поток уведомлений упреждающе завершает условие и вызываетnotifyметод, а затем дождаться выполнения потокаwaitметод, который приводит к тому, что ожидающий поток навсегда остается в очереди ожидания, и никто не переходитnotifyЭто. Так что жду в тредеОпределяем, соблюдены ли условия, и вызываем метод ожиданияи в ветке уведомленийЗавершите условие, вызовите метод уведомленияОба должны быть атомарными операциями, взаимоисключающими друг друга, поэтому используйтетот же замокЧтобы синхронизировать эти две атомарные операции, чтобы избежать неловкой ситуации, когда поток будет ждать вечно.Если не вызывается в синхронизированном блоке
wait,notifyилиnotifyAllметод, то есть он вызывается без получения блокировкиwaitметод, например:对象.wait();бросит
IllegalMonitorStateExceptionнеобычный. -
В синхронизированном блоке кода полученный объект блокировки
wait,notifyилиnotifyAllметод.Другими словами, вы не можете случайно вызвать объект.
wait,notifyилиnotifyAllметод. Например, код в ожидающем потоке выглядит так:synchronized (对象1) { while(条件不满足) { 对象2.wait(); //随便调用一个对象的wait方法 } }Код в ветке уведомлений такой:
synchronized (对象1) { 完成条件 对象2.notifyAll(); }для кода
对象2.wait(), указывая, что текущий поток содержит对象2lock, в то время как текущий поток удерживает对象1lock, поэтому так писать неправильно, и он тоже выкинетIllegalMonitorStateExceptionнеобычный. это значитЕсли текущий поток не удерживает блокировку объекта, он не может вызвать объектwaitспособ снять блокировку. Итак, если вы хотите, чтобы ожидающий поток отказался от удерживаемой в данный момент блокировки, вы можете только вызвать对象1.wait().然后这个线程就被放置到与对象1Связанная очередь ожидания может быть вызвана только в потоке уведомлений.对象1.notifyAll()чтобы уведомить эти ожидающие потоки. -
Ожидая, пока поток определит, выполнено ли условие, следует использовать
while, вместоif.Другими словами, он используется при оценке того, выполняется ли условие.
while:while(条件不满足) { //正确✅ 对象.wait(); }Вместо того, чтобы использовать
if:if(条件不满足) { //错误❌ 对象.wait(); }Это связано с тем, что в многопоточных условиях он может вызываться в одном потоке.
notifyСразу после этого другой поток изменил состояние на неудовлетворенное состояние.Например, в тот момент, когда ремонтник сказал всем идти в туалет после ремонта туалета, там был маленький ребенок, который молниеносно забил туалетную яму , После получения бутылки туалет снова становится недоступным.Те, кто ждет, чтобы воспользоваться туалетом, все еще должны оценить, соблюдены ли условия, прежде чем продолжить. -
После вызова объекта блокировки
notifyилиnotifyAllПосле метода ожидающий поток не сразу возвращается из метода wait().После того, как поток, вызвавший notify() или notifyAll(), снимает блокировку, ожидающий поток возвращается из wait() для продолжения выполнения..То есть, если поток уведомлений завершает вызов объекта блокировки
notifyилиnotifyAllПосле метода идет код, который необходимо выполнить, например:synchronized (对象) { 完成条件 对象.notifyAll(); ... 通知后的处理逻辑 }Необходимо снять блокировку после логики обработки после завершения уведомления, чтобы другие потоки могли
waitСостояние восстанавливается, и блокировка повторно соревнуется для выполнения кода. Например, после того, как ремонтник отремонтировал туалет и оповестил людей, ожидающих выхода в туалет, он не вышел из туалета, но написав на стене туалета что-то вроде «ХХХ, иди сюда», он вышел туалета, из «Когда туалет вышел», это означало, что замок был снят, и брат Гоу, Е Мао и Ван Нимей начали соревноваться за возможность войти в туалет. -
notifyметод удалит только один поток из очереди ожидания, в то время какnotifyAllМетод будет удален из всех нитей в очереди.Вы можете поместить код выше
notifyAllзамена методаnotifyметод, см. результат выполнения~
waitиsleepразница
Зоркие друзья обязательно это найдут,waitиsleepОба эти метода позволяют потоку приостановить выполнение, и обаInterruptedExceptionописание исключения, то в чем между ними разница?
-
waitдаObjectметод члена, в то время какsleepдаThreadстатический метод.Пока это объект блокировки, он может вызывать свой собственный в блоке синхронизированного кода.
waitметод,sleepдаThreadСтатический метод, который позволяет текущему потоку спать в течение указанного времени. -
перечислить
waitМетод должен сначала приобретать замок при вызовеsleepметод не нужен.Еще раз, не забудьте вызвать объект блокировки
waitметод, посылка состоит в том, чтобы получить замок! Суть в том, чтобы получить замок! Суть в том, чтобы получить замок! иsleepметод может быть вызван в любое время -
перечислить
waitПоток метода должен использоватьnotifyпроснуться, иsleepЗначение тайм-аута должно быть установлено. -
поток звонит
waitметод сначала освободит блокировку, аsleepне снимает замок.Это может быть самым важным отличием.Нити Брата Гоу, Лорда Кота и Ван Нимея изначально получили замок унитаза, но назвали
waitПосле метода блокировка автоматически снимается, так что обслуживающий персонал может войти в туалет для обслуживания. Если брат-собака решил позвонить, когда обнаружил, что туалет недоступенsleepЕсли используется этот метод, нить не откроет замок, а это означает, что обслуживающий персонал не сможет получить замок унитаза и, следовательно, не сможет отремонтировать унитаз~ Все должны помнить об этом!
Суммировать
-
Потоки должны взаимодействовать для совместного решения сложной проблемы.
-
Изменяемые общие переменные являются естественным средством связи, но при их использовании должна быть гарантирована безопасность потоков, обычно с использованием
volatileпеременная илиsynchronizedДля обеспечения безопасности потоков. -
После того, как поток получает блокировку, если указанное условие не выполняется, он должен активно отказаться от блокировки, а затем ждать в специальной области ожидания, пока поток не выполнит указанное условие, а затем уведомить поток, ожидающий завершения этого состояние. пусть продолжают. Этот механизм
wait/notifyмеханизм. -
Общий шаблон для ожидающих потоков:
synchronized (对象) { 处理逻辑(可选) while(条件不满足) { 对象.wait(); } 处理逻辑(可选) }Его можно разделить на следующие этапы:
- Получите блокировку объекта.
- Если определенное условие не выполняется, вызовите метод ожидания объекта блокировки и проверьте, выполняется ли условие после получения уведомления.
- Продолжайте выполнение кода, если условие выполнено.
-
Общий шаблон для потока уведомлений:
synchronized (对象) { 完成条件 对象.notifyAll();、 }Его можно разделить на следующие этапы:
- Получает блокировку объекта.
- Условие завершения.
- Уведомлять ожидающие потоки в очереди ожидания.
-
waitиsleepразница-
wait является методом-членом Object, а sleep — статическим методом Thread.
-
Вызов метода ожидания должен сначала получить блокировку, а вызов метода сна — нет.
-
Поток, вызывающий метод ожидания, должен проснуться с уведомлением, а спящий режим должен установить значение тайм-аута.
-
Поток сначала снимет блокировку после вызова метода ожидания, а сон не снимет блокировку.
-
Не по теме
Написание статей очень утомительно, и иногда вы чувствуете, что чтение идет очень гладко, что на самом деле является результатом бесчисленных правок за ним. Если вы считаете, что это хорошо, пожалуйста, помогите переслать его, большое спасибо ~ Вот мой официальный аккаунт, в нем есть больше технических галантерейных товаров, и я буду говорить об этом время от времени Добро пожаловать, чтобы обратить внимание:
буклет
Кроме того, автор также написал буклет MySQL:Ссылка на статью «Как работает MySQL: понимание MySQL у истоков». Содержание буклета в основном с точки зрения Xiaobai, с использованием относительно популярного языка для объяснения некоторых основных концепций расширенного MySQL, таких как записи, индексы, страницы, табличные пространства, оптимизация запросов, транзакции и блокировки и т. д. количество слов составляет от 300 000 до 400 000 слов, с сотнями оригинальных иллюстраций. Основная цель состоит в том, чтобы облегчить обычным программистам изучение MySQL Advanced и сделать кривую обучения более плавной. Студенты, у которых есть сомнения относительно MySQL Advanced, могут взглянуть на: