Производитель потребительский паттерн является наиболее распространенной картиной среди многопоточного чтения: нить производителя (один или несколько) генерирует хлеб и помещает его в корзину (набор или массив), и в то же время потребительский нить (один или несколько) выводит хлеб из корзины (набор или массив) и потребляет его. Хотя у них разные задачи, ресурсы, которые они обрабатывают, одинаковы, что отражает метод межпоточной связи.
Эта статья сначала объяснит ситуацию отдельных производителей и одиночных потребителей, а затем объяснит ситуацию с многопрофильной моделью и многопользовательской моделью. Эти два режима также будут реализованы с использованием механизма wait ()/nofity ()/nofityall () и механизм lock ()/unlock () соответственно.
Перед началом введения шаблона объясните подробности использования методов wait (), notify () и notifyall (), а также улучшенное использование Lock ()/unlock (), wait ()/signal ()/signalall ().
1. Принцип ожидания и механизма пробуждения
Подождите (), уведомление () и уведомление () соответственно представляют потоки, которые входят в сон, разбудите поток сна и разбудите все спальные потоки. Но какой поток является объектом? Кроме того, все три метода, описанные в документации API, должны использоваться в соответствии с достоверным монитором (который можно понимать как удержание блокировки). Какое отношение эти три метода связаны с блокировкой?
В качестве примера можно использовать функции кода синхронизации кода синхронизированного (obj) {} или синхронизации в качестве примера, wate (), notify () и notifyall () может использоваться в своей структуре кода, поскольку все они содержит блокировки.
Для следующих двух блоков кода синхронизации используются блокировки obj1 и obj2. Поток 1 и поток 2 выполняют код синхронизации, соответствующий OBJ1, а поток 3 и поток 4 выполняют код синхронизации, соответствующий OBJ2.
класс Mylock реализует Runnable {public int flag = 0; Объект obj1 = new Object (); Объект obj2 = new Object (); public void run () {while (true) {if (flag%2 = 0) {synchronized (obj1) {// потоки t1 и t2 выполняют эту задачу синхронизации // try {obj1.wait ();} catch (прерывание. synchronized(obj2){ //Thread t3 and t4 perform this synchronization task//try{obj2.wait();}catch(InterruptedException i){} //obj2.notify() //obj2.notifyAll() } } } } } } } }}class Demo { public static void main(String[] args){ MyLock ml = новый mylock (); Потока T1 = новый поток (ML); Потока T2 = новый поток (ML); Поток T3 = новый поток (ML); Потока T4 = новый поток (ML); t1.start (); t2.start (); try {thread.sleep (1)} catch (прерванное искусство i) {}; ml.flag ++; t3.start (); t4.start (); }}Когда T1 начнет выполнять, чтобы ждать (), он войдет в состояние сна, но это не нормальный сон, но сон в пуле потоков, идентифицированный OBJ1 (на самом деле монитор соответствует пулу потоков, но монитор и блокировка связаны в это время). Когда T2 начинает выполнять, он обнаруживает, что блокировка obj1 удерживается другими потоками, и он войдет в состояние сна. На этот раз это потому, что ресурс блокировки ждет, а не сон, введенный в ожидание (). Поскольку T2 уже определил, что он применяется к блокировке OBJ1, он также войдет в сон бассейна obj1, а не на обычный сон. Точно так же, T3 и T4, эти два потока будут входить в бассейн потоков OBJ2, чтобы заснуть.
Когда поток выполняется в уведомлении (), это уведомление () случайным образом разбудит любой поток в пуле потоков, соответствующий его блокировке. Например, obj1.notify () разбудит любую спящую нить в бассейне потоков OBJ1 (конечно, если нет спального потока, то ничего не сделайте). Точно так же notifyall () разбудит все спящие потоки в соответствующем бассейне резьбов блокировки.
Что вы должны выяснить, так это «соответствующая блокировка», потому что блокировка должна быть четко указана при вызове wait (), notify () и notifyall (). Например, obj1.wait (). Если блокировка принадлежит ему, это опущено, это означает этот объект, то есть префиксы этих трех методов могут быть опущены только в нестатических функциях синхронизации.
Короче говоря, когда используется синхронизация, используется блокировка, а поток имеет дом, и вся его основа определяется принадлежном блокировкой. Например, при синхронизации потоков он определяет, не является ли блокировка холостом решающим, чтобы выполнить последующий код, а также определяет, следует ли перейти к определенному пулу потоков, чтобы спать. При пробуждении он разбудит нить в пуле потоков, соответствующий замку.
В применении этих методов, как правило, в задаче, wait () и notify ()/notifyall () появляются парами и выполняйте один за другим. Другими словами, во время этого раунда атомного синхронного выполнения, либо wait () выполняется для сна, либо уведомление () выполняется для разбуждения потока сна в пуле потоков. Чтобы достичь выборочного исполнения, вы можете рассмотреть возможность использования маркировки в качестве основы для суждения. Обратитесь к следующим примерам.
2.зит и состояние
Три метода серии wait () очень ограничены, потому что как спят, так и пробуждающие действия полностью связаны с замком. Например, поток, связанный с блокировкой obj1, может разбудить поток только в пуле потоков OBJ1, но не может разбудить поток, связанный с блокировкой obj2; Например, когда синхронизированная синхронизация была первоначально синхронизирована, блокировка была неявно получена автоматически при начале синхронизации, и после выполнения всей задачи он неявно выпустил блокировку, что означает, что действие получения блокировки и выпуска блокировки не может контролироваться вручную.
Начиная с JDK 1.5, Java предоставляет пакет java.util.concurrent.locks, который предоставляет интерфейс блокировки, интерфейс условий и интерфейс readwritelock. Первые два интерфейса отделяют методы блокировки и монитора (сна, пробуждение). Интерфейс блокировки предоставляет только блокировки. С помощью метода блокировки NewConditon () можно создать один или несколько мониторов, связанных с блокировкой. У каждого монитора есть свои методы сна и пробуждения. Другими словами, Lock заменяет использование синхронизированных методов и синхронизированных кодовых блоков, а состояние заменяет использование методов монитора объектов.
Как показано на рисунке ниже:
Когда поток выполняет Condity1.await (), поток введет пул потоков, соответствующий монитору Condition1 для сна. Когда Condity1.Signal () выполняется, любой поток в пуле потоков Condition1 будет пробужден случайным образом. Когда будет выполнено условия1.signalall (), все потоки в пуле потоков Condition1 будут пробуждены. Точно так же то же самое верно для монитора Condition2.
Даже если есть несколько мониторов, если они связаны с одним и тем же объектом блокировки, другой поток может работать через монитор. Например, поток в условиях1 может выполнять условия2.signal () для разбуждения потока в пуле потоков Condition2.
Чтобы использовать этот способ ассоциации замков и мониторов, обратитесь к следующим шагам:
import java.util.concurrent.locks.*; lock l = new Reentrantlock (); условие con1 = l.newcondition (); условие con2 = l.newcondition (); l.lock (); try {// кодовый сегмент, содержащий wait (), сигнал () или сигнал () ...}, наконец, {l.unlock (); // Поскольку сегмент кода может быть ненормальным, должен быть выполнен разблокировка (), необходимо использовать и разблокировать () должен быть помещен в наконец -то сегмент}Для конкретного использования см. Пример кода для блокировки и условия позже.
3. Модель однородного потребителя с одним производителем
Тема производителя, потребительская нить. Для каждого хлеба, производимого производителем, положите его на тарелку, потребитель вынимает хлеб с тарелки для потребления. Основа для производителей судить о том, следует ли продолжать производство, заключается в том, что на тарелке нет хлеба, в то время как потребители должны судить, нужно ли потреблять, заключается в том, что на тарелке есть хлеб. Поскольку в этом режиме только одна буханка хлеба всегда помещается на тарелку, тарелка может быть опущена, а производитель и потребитель могут сдать хлеб шаг за шагом.
Во -первых, нам нужно описать эти три категории: одна из них - ресурс, управляемый несколькими потоками (здесь хлеб), вторым является производитель, а третий - потребитель. В следующем примере я инкапсуляю методы производства хлеба и потребления хлеба в классах производителей и потребителей соответственно, что легче понять, если они инкапсулируются в классе хлеба.
// Описание ресурс: имя и количество хлеба, определяемое количеством хлеба хлеба {public String name; public int count = 1; Общественный логический флаг = false; // Этот знак предоставляет оценки суждения для wait () и notify ()} // Хлебский ресурс, обработанный производителем и потребителем, одинаковы. Чтобы обеспечить это, // класс хлеба может быть разработан в соответствии с шаблоном Singleton, или тот же хлеб -объект может быть передан производителю и потребителю с помощью метода строительства. Последний метод используется здесь. // Опишите продюсер продюсер продюсера реализует Runnable {Private Bread B; // член производителя: ресурс, который он хочет обрабатывать производителя (хлеб б) {this.b = b; } // Предоставление метода производства хлеба Public void Produce (String name) {b.name = name + b.count; B.Count ++; } public void run () {while (true) {synchronized (hread.class) {// Использовать хлеб. try {rab.class.wait ();} catch (прерванное искусство i) {}} продукт ("хлеб"); System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // notify () также должен быть синхронизирован, в противном случае блокировка была выпущена, а действие по пробуждению не может быть выполнено // ps: в задаче синхронизации wait () и notify () следует выполнять только, иначе нить другой стороны будет запутана}}}} // Опишите потребительский класс. this.b = b; System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ // 2.Окончательный результат выполнения должен быть получен и потребляется, и это непрерывный цикл. следующее:
Thread-0---Producer----Bread1Thread-1---Consumer-------Bread1Thread-0---Producer----Bread2Thread-1--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4. Используйте блокировку и условия для реализации единичной модели производства и потребления
Код заключается в следующем:
Импорт java.util.concurrent.locks.*; класс хлеб {public String name; public int count = 1; Общественный логический флаг = false; // Предоставьте тот же объект блокировки и тот же объект условий для производителей и потребителей, общественный статический блокировка блокировки = new Reentrantlock (); Общественное статическое условие условия = lock.newcondition ();} Производитель класса реализует Runnable {частный хлеб B; Производитель (хлеб B) {this.b = b; } public void продукт (String name) {b.name = name + b.count; B.Count ++; } public void run () {while (true) {// Использовать хлеб. try {if (b.flag) {try {rab.condition.await ();} catch (прерывание Exception i) {}} продукт ("хлеб"); System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Hread.condition.Signal (); if (! b.flag) {try {hab.condition.await ();} catch (прерывание экзэпмента i) {}} System.out.println(Thread.currentThread().getName()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // 2.5. Модель многопроизводства и потребления (отдельный хлеб)
Здесь мы сначала объясняем модель нескольких производителей и нескольких потребителей, но не более одного хлеба одновременно. Эта модель может быть не идеальной в реальности, но для того, чтобы привести к реальной модели многопроизводства и множественного потребления позже, я думаю, что необходимо объяснить эту модель здесь и проанализировать эту модель и то, как она развивалась из кода одного производства и единого потребления.
Как показано на рисунке ниже:
От единого производства и единого потребления до множественного производства и множественного потребления, из-за проблем безопасности многопоточных и проблем с тупиком, есть две проблемы, которые необходимо учитывать:
Для одной из сторон, как многопоточности могут достичь той же производственной или потребления, что и однопоточное? Другими словами, как сделать многопоточный вид однопоточного. Самая большая разница между многопоточным и однопоточным образом-это проблемы безопасности с многопотокой. Следовательно, до тех пор, пока вы гарантируете, что задачи, выполняемые с помощью многопоточности, могут быть синхронизированы.
Первый вопрос рассматривает проблему мультипотового на одной стороне, а во втором вопросе рассматривается, как две стороны могут гармонично сотрудничать для завершения производства и потребления. То есть, как обеспечить, чтобы одна сторона производителя и потребителя спала, а другая сторона активна. Просто разбудите другую сторону, когда одна сторона закончила выполнять задачу синхронизации.
Фактически, от единого потока до многопоточного, есть две проблемы, которые необходимо рассмотреть: вне синхронизации и тупика. (1) Когда как производитель, так и потребительская сторона имеют многопотоковые, многопоточные продюсера можно рассматривать как поток в целом, а также многопоточные точки потребительской стороны также в целом, что решает проблему безопасности потока. (2) Объединение всего производства и всего потребителя считается многопоточным, чтобы решить проблему тупика. Способ решить тупик в Java - разбудить другую вечеринку или разбудить все.
Вопрос в том, как обеспечить синхронизацию между несколькими потоками определенной стороны? Код одного потребителя анализируется с помощью многопоточного выполнения в качестве примера.
while (true) {synchronized (bread.class) {if (! b.flag) {try {hab.class.wait ();} catch (прерванная экзенция i) {}} System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Предположим, что потока потребления 1 разбудит потоку потребления 2 после употребления буханки хлеба, и продолжает зацикливаться, судить, если (! Флаг) он подождает, и замок будет выпущен. Предполагая, что процессор просто выбирает потребительский поток 2, потребительский поток 2 также войдет в ожидание. Когда производитель производит буханку хлеба, предположим, что нить 1 потребления пробуждается, он будет продолжать потреблять недавно произведенный хлеб из заявления ожидания, предположим, что нить 2 потребления снова пробуждается. Когда поток потребления 2 выбрана процессором, потребление нить 2 также будет потреблять вниз от оператора ожидания, и только что произведенный хлеб потребляется. Проблема возникает снова. Непрерывно пробужденные потоки потребления 1 и 2 потребляют один и тот же хлеб, что означает, что хлеб неоднократно потребляется. Это еще одна многопоточная проблема.
После долгого разговора об этом, на самом деле очень просто анализировать после увеличения линии обзора. До тех пор, пока две или более нитей одной стороны ждут суждения B.Flag, то два или более потоков могут быть непрерывно пробуждены и продолжают производиться или потреблять вниз. Это создает проблему многопоточной синхронизации.
Проблема неуверенности заключается в том, что несколько потоков на одной стороне продолжают производить или потреблять вниз после непрерывного пробуждения. Это вызвано оператором IF. Если поток ожидания может повернуться назад, чтобы определить, является ли B.Flag верным после пробуждения, это может заставить его решить, продолжать ожидание, производство или потребление вниз.
Вы можете заменить оператор IF на оператор WICH, чтобы удовлетворить требования. Таким образом, независимо от того, непрерывно пробуждены несколько потоков на определенной стороне, они вернется к судье B.Flag.
while (true) {synchronized (bread.class) {while (! b.flag) {try {hab.class.wait ();} catch (прерванная экспрессия i) {}} System.out.println(Thread.currentThread().getName()+"------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Первая многопоточная проблема безопасности была решена, но возникли проблемы с тупиком. Это легко проанализировать. Производитель считается в целом, а потребитель также является целым. Когда все потоки продюсера ждут (потоки производственной партии непрерывно пробуждаются, все потоки вечеринки будут ждать), и потребитель также ждет, и появится тупик. На самом деле, если вы смотрите на это в усиленном способе, производитель и потребитель считаются одним потоком соответственно. Эти два потока образуют несколько потоков. Когда одна сторона ждет и не может разбудить другую сторону, другая сторона обязательно подождет, так что она будет заложена в тупик.
За проблему тупика между обеими сторонами, если вы гарантируете, что другая сторона может быть пробуждена, а не непрерывное пробуждение одной стороны, ее можно решить. Просто используйте notifyall () или signalall (), или вы можете разбудить другой поток через Signal (), чтобы решить проблему. Смотрите второй код ниже.
Согласно вышеуказанному анализу, если модель кода единого производства и единого потребления улучшится, его можно изменить на модель с мультипродукцией и мультипотреблением отдельного хлеба.
// Сегмент кода 1CLASS Хлеб {public String name; public int count = 1; Общественный логический флаг = false; } // Опишите производитель производителя, реализует Runnable {Private Bread B; Производитель (хлеб B) {this.b = b; } public void продукт (String name) {b.name = name + b.count; B.Count ++; } public void run () {while (true) {synchronized (hab.class) {while (b.flag) {try {hab.class.wait ();} catch (прерывание I) {}} продукт ("хлеб"); System.out.println(Thread.currentThread().getName()+"--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Потребление строки) {return b.name; System.out.println(Thread.currentThread().getName()+"--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ } public Class Newconsume_5 { 2 Thread Con_T1 = новая поток (CON);Ниже приводится рефактор кода с использованием блокировки и conditon, используя Signal () для разбуждения другого потока.
// Сегмент кода 2IMPORT java.util.concurrent.locks.*; Класс хлеб {public String name; public int count = 1; Общественный логический флаг = false; Public Static Lock Lock = new Reentrantlock (); Общественное статическое условие pro_con = lock.newcondition (); Общественное статическое условие con_con = lock.newCondition ();} // Опишите производитель производителя, реализует Runnable {Private Bread B; Производитель (хлеб B) {this.b = b; } public void продукт (String name) {b.name = name + b.count; B.Count ++; } public void run () {while (true) {bread.lock.lock (); try {while (b.flag) {try {rab.pro_con.await ();} catch (прерывание экзэпмента i) {}} продукта ("хлеб"); System.out.println(Thread.currentThread().getName()+"----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Хлеб. while (! b.flag) {try {hared.con_con.await ();} catch (прерывание. I) {}} System.out.println(Thread.currentThread().getName()+"-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- //2. Create producer and consumer objects Producer pro = new Producer(b); Consumer con = new Consumer(b); //3. Create thread object Thread pro_t1 = new Thread(pro); Thread pro_t2 = new Thread(pro); Thread con_t1 = new Thread(con); Thread con_t2 = new Thread(con); pro_t1.start(); pro_t2.start(); con_t1.start(); con_t2.start ();Давайте суммируем проблемы большего производства и большего потребления:
(1). Решение многопоточной синхронизации определенной стороны состоит в том, чтобы использовать (флаг), чтобы определить, ожидание;
(2). Решение проблемы обеих сторон - разбудить другую сторону. Вы можете использовать notifyall (), signalall () или метод Signal () монитора другой стороны.
6. Больше моделей производства и потребления
Есть несколько потоков производителей и несколько потребительских потоков. Производитель помещает изготовленный хлеб в корзину (набор или массив), а потребитель выводит хлеб из корзины. Основа для производителей судить о продолжающемся производстве заключается в том, что корзина заполнена, и для потребителей основывается судить о дальнейшем потреблении, является ли корзина пустой. Кроме того, когда потребитель вынимает хлеб, соответствующая позиция снова становится пустой, и производитель может повернуть назад и продолжить производство из исходной позиции корзины, что может быть достигнуто путем сброса указателя корзины.
В этой модели, в дополнение к описанию производителей, потребителей и хлеба, также необходимо описать контейнер для корзины. Предположим, что массив используется в качестве контейнера, каждый раз, когда производитель производит один, производственный указатель смещается назад, и каждый раз, когда потребитель потребляет один, указатель потребления сменяется назад.
Код следующим образом: вы можете обратиться к примеру кода, приведенному в классе API-> Condition Class
Import java.util.concurrent.locks.*; Class Casport {Private Bread [] arr; // размер корзины корзины (int size) {arr = новый хлеб [размер]; } // Указатель IN и OUT private int in_ptr, out_ptr; // сколько хлеба осталось в корзине частной int Left; Private Lock Lock = new Reentrantlock (); частное условие полное = lock.newCondition (); частное условие пустое = lock.newCondition (); // хлеб в корзину public void in () {lock.lock (); try {while (left == arr.length) {try {full.await ();} catch (прерванное искусство i) {i.printstacktrace ();}} arr [in_ptr] = new Bread ("mianbao", производитель. num ++); System.out.println («Поместите хлеб:»+arr [in_ptr] .getName ()+»------- в корзину ["+in_ptr+"]"); слева ++; if (++ in_ptr == arr.length) {in_ptr = 0;} empty.signal (); } наконец {lock.unlock (); }} // Хлеб из корзины общественного хлеба () {lock.lock (); try {while (left == 0) {try {umpty.await ();} catch (прерванное искусство i) {i.printstacktrace ();}} хлеб out_bread = arr [out_ptr]; System.out.println ("Получите хлеб:"+out_bread.getName ()+"---------- от корзины ["+out_ptr+"]"); левый--; if (++ out_ptr == arr.length) {out_ptr = 0;} full.signal (); вернуть out_bread; } наконец {lock.unlock (); }}} класс хлеб {private String name; Хлеб (string name, int num) {this.name = name + num; } public String getName () {return this.name; }} Производитель класса реализует Runnable {Private Basket Basket; общественный статический int num = 1; // первый номер для имени хлеба (корзина b) {this.basket = b; } public void run () {while (true) {baske.in (); try {thread.sleep (10);} catch (прерывание Extexception i) {}}}} класс потребительские реализует runnable {частная корзина корзины; частный хлеб i_get; Потребитель (корзина b) {this.basket = b; } public void run () {while (true) {i_get = baske.out (); try {thread.sleep (10);} catch (прерывание Exception i) {}}}} public classeeconsume_7 {public static void main (string [] args) {корзина b = новая корзина (20); // размер корзины = 20 Производитель Pro = новый производитель (B); Потребитель con = новый потребитель (б); Thread Pro_t1 = новый поток (Pro); Thread Pro_t2 = новый поток (Pro); Thread con_t1 = новый поток (con); Thread con_t2 = новый поток (con); Thread con_t3 = новый поток (con); pro_t1.start (); pro_t2.start (); con_t1.start (); con_t2.start (); con_t3.start (); }}Это включает в себя потребителей, производителей, хлеб и корзины, где хлеб и корзины - это ресурсы, управляемые несколькими потоками. Нить производителя производит хлеб и помещает его в корзину, а потребительская нить извлекает хлеб из корзины. Идеальный код состоит в том, чтобы инкапсулировать как производственные задачи, так и задачи потребления в класс ресурсов. Поскольку хлеб является элементом контейнера для корзины, он не подходит для упаковки в класс хлеба, а упаковка в корзину облегчает эксплуатацию контейнера.
Обратите внимание, что вы должны поместить все коды, включающие ресурсные операции внутри блокировки, в противном случае возникнет проблема с многопоточной синхронизацией. Например, метод, производящий хлеб, определяется в классе производителей, а затем используется в качестве параметра для метода корзины. In (), помещенного в корзину, то есть, capta.in (производитель ()), который является неправильным поведением, поскольку производитель () передается в метод () после его выполнения за пределами блокировки.
Приведенная выше статья основана на модели Java Producer и Consumer Model (подробный анализ) и представляет собой весь контент, разделенный редактором. Я надеюсь, что это может дать вам ссылку, и я надеюсь, что вы сможете поддержать Wulin.com больше.