Який метод переводить потік у режим очікування




Який метод переводить потік у режим очікування



методи очікування та повідомлення() в Java

У цьому уроці ми розглянемо один із найбільш фундаментальних механізмів Java — синхронізацію потоків.

Спочатку ми обговоримо деякі важливі терміни та методології, пов'язані з паралелізмом.

І ми розробимо простий додаток, в якому ми будемо мати справу з проблемами паралелізму, з метою кращого розуміння wait() та notify().

2. Синхронізація потоків у Java

У багатопотоковій середовищі кілька потоків можуть спробувати змінити той самий ресурс. Неправильне керування потоками, звичайно, призведе до проблем узгодженості.

2.1. Блоки, що охороняються в Java

Одним із інструментів, який ми можемо використовувати для координації дій кількох потоків Java, є захищені блоки. Такі блоки перевіряють певну умову перед поновленням виконання.

Маючи це на увазі, ми будемо використовувати наступне:

  • Object.wait() для припинення потоку
  • Object.notify() для пробудження потоку

Ми можемо краще зрозуміти це з наступної діаграми, що зображає життєвий цикл Thread:

Зверніть увагу, що є безліч способів управління цим життєвим циклом. Однак у цій статті ми зосередимося тільки на wait() та notify() .

3. Метод очікування ()

Простіше кажучи, виклик wait() змушує поточний потік чекати, поки інший потік не викличе notify( ) або notifyAll() для того ж об'єкта.

Для цього поточний потік має володіти монітором об'єкта. Відповідно до Javadocs це може статися такими способами:

  • коли ми виконали метод синхронізованого екземпляра для даного об'єкта
  • коли ми виконали тіло синхронізованого блоку для цього об'єкта
  • шляхом виконання синхронізованих статичних методів для об'єктів типу Class

Зауважте, що лише один активний потік може одночасно володіти монітором об'єкта.

Цей метод wait() поставляється із трьома перевантаженими сигнатурами. Погляньмо на це.

3.1. чекати()

Метод wait() змушує поточний потік нескінченно чекати, поки інший потік не викликає notify( ) для цього об'єкта або notifyAll() .

3.2. чекати (тривалий тайм-аут)

Використовуючи цей метод, ми можемо вказати тайм-аут, після якого потік автоматично розбудиться. Потік можна розбудити до досягнення тайм-ауту за допомогою notify( ) або notifyAll() .

Зверніть увагу, що виклик wait(0) аналогічний виклику wait() .

3.3. очікування (довгий тайм-аут, int nanos)

Це ще один підпис, що забезпечує ту саму функціональність. Єдина різниця тут у тому, що ми можемо забезпечити більш високу точність.

Загальний період очікування (у наносекундах) розраховується як 1_000_000*timeout + nanos.

4. повідомити() та повідомити всіх()

Ми використовуємо метод notify() для пробудження потоків, які чекають на доступ до монітора цього об'єкта.

Існує два способи сповіщення очікуваних потоків.

4.1. повідомляти()

Для всіх потоків, що чекають на моніторі цього об'єкта (використовуючи будь-який з методів wait() ), метод notify() повідомляє будь-який з них про довільне пробудження. Вибір того, який саме потік розбудити недетермінований і залежить від реалізації.

Оскільки notify() пробуджує один випадковий потік, ми можемо використовувати його для реалізації взаємного блокування, коли потоки виконують схожі завдання. Але в більшості випадків було б доцільніше реалізувати notifyAll() .

4.2. повідомити всіх ()

Цей метод просто пробуджує всі потоки, що очікують моніторі цього об'єкта.

Прокидані потоки завершаться звичайним чином, як і будь-які інші потоки.

Але перш ніж дозволити їх виконання, завжди визначайте швидку перевірку умови, необхідної для роботи з потоком. Це з тим, що може бути ситуації, коли потік був збуджений без отримання повідомлення (цей сценарій обговорюється пізніше у прикладі).

5. Проблема синхронізації відправника та одержувача

Тепер, коли ми познайомилися з основами, розглянемо простий додаток Sender - Receiver , який буде використовувати методи wait() і notify() для налаштування синхронізації між ними:

  • Відправник повинен надіслати пакет даних Одержувачу.
  • Отримувач не може обробити пакет даних, поки Відправник не завершить його надсилання. ``
  • Так само відправник не повинен намагатися відправити інший пакет, якщо одержувач вже не обробив попередній пакет.

Давайте спочатку створимо клас Data, що складається з пакета даних, який буде відправлений від Sender до Receiver. Ми будемо використовувати wait() і notifyAll() для налаштування синхронізації між ними:


public
class
Data



private
String
packet;



// True if receiver should wait


// False if sender should wait


private
boolean
transfer
=
true;



public
synchronized
String
receive()



while
(transfer)



try



wait();


>
catch
(InterruptedException
e)



Thread.currentThread().interrupt();


System.out.println("Thread Interrupted");


>


>


transfer
=
true;



String
returnPacket
=
packet;


notifyAll();


return
returnPacket;


>



public
synchronized
void
send(String
packet)



while
(!transfer)



try



wait();


>
catch
(InterruptedException
e)



Thread.currentThread().interrupt();


System.out.println("Thread Interrupted");


>


>


transfer
=
false;



this.packet
=
packet;


notifyAll();


>


>

Давайте розберемо, що тут відбувається:

  • Змінна пакета позначає дані, що передаються мережею.
  • У нас є логічна змінна transfer, яку Відправник та Одержувач будуть використовувати для синхронізації:
  • Якщо ця змінна має значення true , Отримувач повинен чекати, доки Відправник надішле повідомлення.
  • Якщо це false, відправник повинен чекати, поки одержувач отримає повідомлення.
  • Відправник використовує метод send ( ) для надсилання даних одержувачу:
  • Якщо transfer дорівнює false , ми чекатимемо, викликавши wait() у цьому потоці.
  • Але коли це правда, ми перемикаємо статус, встановлюємо наше повідомлення і викликаємо notifyAll() , щоб розбудити інші потоки, щоб вказати, що сталося важлива подія, і вони можуть перевірити, чи можуть вони продовжити виконання.
  • Так само Receiver буде використовувати метод receive() :
  • Якщо Sender встановив для передачі значення false тільки тоді вона продовжиться, інакше ми викличемо wait() в цьому потоці.
  • Коли умова виконується, ми перемикаємо статус, повідомляємо всі потоки про пробудження, що очікують, і повертаємо отриманий пакет даних.

5.1. Навіщо укладати очікування() у цикл while?

Оскільки notify( ) і notifyAll() випадково пробуджують потоки, що очікують на моніторі цього об'єкта, виконання умови не завжди важливо. Іноді потік прокидається, але умова насправді ще не виконана.

Ми також можемо визначити перевірку, щоб уберегти нас від хибних пробуджень, коли потік може вийти з очікування, навіть не отримавши сповіщення.

5.2. Навіщо нам потрібно синхронізувати методи end() та Receive()?

Ми розмістили ці методи всередині синхронізованих методів, щоб забезпечити вбудовані блокування. Якщо потік, що викликає метод wait() , не має вбудованого блокування, буде видана помилка.

Тепер ми створимо Sender та Receiver та реалізуємо інтерфейс Runnable для обох, щоб їх екземпляри могли виконуватися потоком.

Спочатку подивимося, як працюватиме Sender :


public
class
Sender
implements
Runnable



private
Data
data;



// standard constructors



public
void
run()



String
packets[]
=



"First packet",


"Second packet",


"Third packet",


"Fourth packet",


"End"


>;



for
(String
packet
:
packets)



data.send(packet);



// Thread.sleep() to mimic heavy server-side processing


try



Thread.sleep(ThreadLocalRandom.current().nextInt(1000,
5000));


>
catch
(InterruptedException
e)



Thread.currentThread().interrupt();


Log.error("Thread interrupted",
e);


>


>


>


>

Давайте уважніше подивимося на цього відправника:

  • Ми створюємо кілька випадкових пакетів даних, які відправлятимуться по мережі як масиву пакетів [] .
  • Для кожного пакету ми просто викликаємо send().
  • Потім ми викликаємо Thread.sleep() з випадковим інтервалом, щоб імітувати важку обробку за сервера.

Нарешті, давайте реалізуємо наш Receiver:


public
class
Receiver
implements
Runnable



private
Data
load;



// standard constructors



public
void
run()



for(String
receivedMessage
=
load.receive();


!"End".equals(receivedMessage);


receivedMessage
=
load.receive())




System.out.println(receivedMessage);



// .


try



Thread.sleep(ThreadLocalRandom.current().nextInt(1000,
5000));


>
catch
(InterruptedException
e)



Thread.currentThread().interrupt();


Log.error("Thread interrupted",
e);


>


>


>


>

Тут ми просто викликаємо load.receive() у циклі, поки не отримаємо останній пакет даних End.

Давайте тепер подивимося на цю програму в дії:


public
static
void
main(String[]
args)



Data
data
=
new
Data();


Thread
sender
=
new
Thread(new
Sender(data));


Thread
receiver
=
new
Thread(new
Receiver(data));



sender.start();


receiver.start();


>

Ми отримаємо наступний висновок:

First packet
Second packet
Third packet
Fourth packet

І ось ми тут. Ми отримали всі пакети даних у правильному послідовному порядку та успішно встановили правильний зв'язок між відправником та одержувачем.

6. Висновок

У цій статті ми обговорили деякі основні концепції синхронізації Java. Зокрема, ми зосередилися на тому, як ми можемо використовувати wait() та notify() для вирішення цікавих проблем із синхронізацією. Нарешті, ми розглянули приклад коду, у якому застосували ці концепції практично.

Перш ніж ми закінчимо, варто згадати, що всі ці низькорівневі API, такі як wait() , notify( ) і notifyAll() є традиційними методами, які добре працюють, але механізми вищого рівня часто простіше і краще — наприклад, Java.рідні інтерфейси Lock and Condition (доступні в пакеті java.util.concurrent.locks).

Для отримання додаткової інформації про пакет java.util.concurrent відвідайте наш огляд статті java.util.concurrent. Блокування та умова описані в посібнику java.util.concurrent.Locks .

Як завжди, повні фрагменти коду, які використовуються в цій статті, доступні на GitHub.

Російські Блоги

пояснення
Потік включає наступні 5 станів.
1. Новий статус (Новий): Після створення об'єкта потоку він перетворюється на новий стан. Наприклад, Thread thread = New Thread ().
2. Стан готовності (Runnable)Також називається "виконуваним станом". Після створення потоку об'єкта інші потоки викликають метод start () об'єкта, щоб запустити потік. Наприклад, thread.start(). Потік у стані готовності може бути запланований до виконання процесором у час.
3. Поточний статусПотік: потік отримує роздільну здатність процесора для виконання. Слід зазначити, що потоки можуть переходити до робочого стану тільки зі стану готовності.
4. Заблоковані (Blocked)Стан блокування такий, що потік чомусь відмовляється від використання процесора і тимчасово зупиняється. Поки потік не перейде у стан готовності, немає жодного шансу перейти у робочий стан. Є три ситуації блокування:
(01) Очікування блокування. Викликавши метод wait() потоку, дайте потоку дочекатися завершення завдання.
(02) Блокування синхронізації - потік не може отримати синхронізоване блокування синхронізації (оскільки блокування зайняте іншим потоком), він перейде в стан блокування синхронізації.
(03) Інше блокування - викликавши sleep() або join() потоку або виконавши запит введення-виведення, потік перейде в стан блокування. Коли час очікування сну () закінчується, join () очікує завершення або закінчення часу очікування потоку, або завершення обробки введення/виведення, потік повертається у стан готовності.
5. стан смерті (Dead): Потік завершує виконання або виходить з методу run () за винятком, і потік завершує свій життєвий цикл.

1.wait(), notify(), notifyAll() та інші методи

У Object.java визначені інтерфейси, такі як wait(), notify() та notifyAll(). Роль wait() - перевести поточний потік у стан очікування. У той же час wait() також дозволить поточному потоку зняти блокування, яке він утримує. Функція notify() та notifyAll() призначена для пробудження очікуваного потоку на поточному об'єкті, notify() для пробудження одного потоку, а notifyAll() для пробудження всіх потоків.

Подробиці API про очікування / пробудження в класі Object наступні:
notify() - Прокинься один потік, що очікує на цьому моніторі об'єкта.
notifyAll() -Буде всі потоки, що очікують на цьому об'єкті монітора
wait() Поточний потік знаходиться в стані очікування (блокування) і до тих пір, поки інші потоки не викличуть метод notify () або метод notify All () цього об'єкта, і поточний потік прокидається (в стан готовності).
wait(long timeout) - Залишити поточний потік у стані очікування (блокування), поки інший потік не викличе метод об'єкта notify () або notify All () або не перевищить вказану кількість часу, і поточний потік прокинеться (в стан готовності)).
wait(long timeout, int nanos) - залишити поточний потік у стані очікування (блокування), поки інший потік не викличе метод об'єкта notify () або notify All (), або якийсь інший потік не перерве поточний потік або не перевищить певну кількість часу. (Увійшов у «стан готовності»).

2. Чому такі функції, як notify(), wait() визначені в Object, а не Thread?

Об'єкт wait(), notify() та інші функції, такі як синхронізовані, працюватимуть із «блокуванням синхронізації об'єкта».

wait () змусить «поточний потік» чекати, оскільки потік переходить у стан очікування, тому потік повинен звільнити «синхронізуюче блокування», яке утримується його блокуванням, в іншому випадку інші потоки не можуть отримати «синхронізуюче блокування» і не можуть працювати!
ОК, після того як потік викличе wait (), він зніме «синхронне блокування», яке утримується його блокуванням, більше того, згідно з попереднім введенням ми знаємо, що очікуваний потік може бути повідомлений () або notifyAll () Прокинься. Тепер подумайте над питанням: на якій підставі notify () будить очікуваний потік? Іншими словами, який зв'язок між wait() та notify()? Відповідь така: згідно з «блокуванням синхронізації об'єкта».

Нитка, відповідальна за пробудження нитки, що чекає (ми називаємо це "Пробудження потоку"), Він отримує тільки "синхронізацію об'єкта" (Блокування синхронізації тут має співпадати з блокуванням синхронізації очікуваного потоку.) І метод notify() або notifyAll() викликається для пробудження очікуваного потоку. Незважаючи на те, що очікуваний потік пробуджений, він не може бути виконаний негайно, тому що потік, що викликає, також містить «синхронізуючу блокування для цього об'єкта». Необхідно зачекати, поки потік пробудження звільнить «блокування синхронізації об'єкта», перш ніж очікуваний потік зможе отримати «блокування синхронізації об'єкта» і продовжити роботу.

Коротше кажучи, notify(), wait() залежать від «синхронних блокувань», а «синхронні блокування» утримуються об'єктними блокуваннями, і кожен об'єкт має лише одне! Ось чому такі функції, як notify(), wait() визначені в класі Object, а не в класі Thread.

3. Введення в урожай ()

Ефект yield() полягає в тому, щоб здатися.Він дозволяє поточному потоку входити в стан готовності з робочого стану, тим самим дозволяючи іншим очікуваним потокам з тим же пріоритетом отримувати права на виконання, однак немає гарантії, що після того, як поточний потік викличе yield (), інші мають такий ж пріоритет Безперечно отримає правильне виконання, може бути, що поточний потік увійшов у «стан виконання», щоб продовжити роботу!

4. Порівняння yield() та wait()

Ми знаємо wait() Його роль полягає в тому, щоб дозволити поточному потоку увійти в стан очікування (блокування) з робочого стану, а також зняти блокування синхронізації. Роль yield () полягає у тому, щоб здатися. Він також залишить поточний потік поза «робочим станом». Відмінності:
(01) wait () дозволяє потоку переходити в стан очікування (блокування) з робочого стану, а yield () - щоб потік входив з робочого стану в Готовий. "
(02) wait() зніме блокування синхронізації об'єкта, який він містить, а метод yield() не зніме блокування.

// Вихідний код YieldLockTest.java public class YieldLockTest < private static Object obj = new Object(); public static void main(String[] args)< ThreadA t1 = New ThreadA("t1"); ThreadA t2 = New ThreadA("t2"); t1.start(); t2.start(); >static class ThreadA extends Thread < public ThreadA(String name)< super(name); >public void run() < // Отримати блокування синхронізації об'єкта obj synchronized (obj) < for(int i=0; i > > > >

(Іноді) результат виконання

t1 [5]:0 t1 [5]:1 t1 [5]:2 t1 [5]:3 t1 [5]:4 t1 [5]:5 t1 [5]:6 t1 [5]:7 t1 [ 5]:8 t1 [5]:9 t2 [5]:0 t2 [5]:1 t2 [5]:2 t2 [5]:3 t2 [5]:4 t2 [5]:5 t2 [5] :6 t2 [5]:7 t2 [5]:8 t2 [5]:9

Пояснення результатів
Два потоки t1 і t2 запускаються переважно потоці main. t1 і t2 будуть посилатися на блокування синхронізації того ж об'єкта в run(), яке синхронізується (obj). Під час роботи t1, хоча він буде викликати Thread.yield(), проте t2 не отримає права на виконання процесора.Тому що t1 не випустив "синхронізацію, що утримується obj"!

5. сон () введення

sleep() визначається в Thread.java.
sleep() використовується для очікування поточного потоку, тобто поточний потік зміниться з "Поточний статус"Перейти до"Сплячий (блокуючий) стан Msgstr "Sleep() буде вказувати час очікування, а час очікування потоку буде більше / дорівнює часу очікування; коли потік знову прокинеться, він буде змінений на "Заблокований стан "Стан готовності Msgstr "Дочекатися запланованого виконання процесора.

6. Порівняння сну () та очікування ()

Ми знаємо, що роль wait() полягає в тому, щоб дозволити поточному потоку увійти в стан очікування (блокування) з робочого стану, а також зняти блокування синхронізації. Роль sleep() полягає в тому, щоб поточний потік перейшов у стан сну (блокування) зі стану роботи.
Однак wait() знімає блокування синхронізації об'єкта, а sleep() не знімає блокування.
У наступному прикладі показано, що sleep() не знімає блокування.

// Вихідний код SleepLockTest.java SleepLockTest < private static Object obj = new Object(); public static void main(String[] args)< ThreadA t1 = New ThreadA("t1"); ThreadA t2 = New ThreadA("t2"); t1.start(); t2.start(); >static class ThreadA extends Thread < public ThreadA(String name)< super(name); >public void run() < // Отримати блокування синхронізації об'єкта obj synchronized (obj) < try < for(int i=0; i > catch (InterruptedException e) < e.printStackTrace(); >> > > > >

Два потоки t1 і t2 запускаються у головному потоці main. t1 і t2 будуть посилатися на блокування синхронізації того ж об'єкта в run(), яке синхронізується (obj). Під час роботи t1, хоча він викличе Thread.sleep (100), проте t2 не отримає права виконання процесора. Тому що t1 не випустив "синхронізацію, що утримується obj"!
Зауважте, що якщо ми закоментуємо синхронізований (obj) і знову запустимо програму, t1 і t2 можна перемикати один на одного.

Посібник з Java Core. Багатопотоковість. Взаємодія потоків.

У реальних додатках ми часто стикаємося із ситуацією, коли два потоки обмінюються якоюсь інформацією.

Для забезпечення цього процесу застосовуються такі методи, як

  • public void wait()
    Цей метод переводить потік у режим очікування до моменту виклику методу notify().
  • public void notify()
    Цей метод переводить один потік з режиму очікування активний режим.
  • public void notifyAll()
    Цей метод переводить всі потоки, які перебувають у режимі очікування активний режим.

Всі ці методи реалізовані як фінальні методи класу Objectщо означає їх доступність для будь-якого класу в мові програмування Java. Всі ці методи можуть бути використані тільки в контексті синхронізації.

Для розуміння того, як це працює на практиці, розглянемо приклад простого застосування.

public class ThreadInteraction < boolean isActive = false; public synchronized void Request(String request)< if(isActive)< try < wait(); >catch (InterruptedException e) < e.printStackTrace(); >> System.out.println("Pilot:"); System.out.println(request); isActive = true; notify(); > public synchronized void Response(String response)< if(!isActive)< try < wait(); >catch (InterruptedException e) < e.printStackTrace(); >> System.out.println("Controller:"); System.out.println(response); isActive = false; notify(); > >
public class Request implements Runnable < ThreadInteraction interaction; String[] request = < "Dnipro Radar, Aeroflot 1816", "Request descent, Aeroflot 1816", "Descending to altitude 6,000 feet, Aeroflot 1816" >; public Request(ThreadInteraction interaction) < this.interaction = interaction; new Thread(this, "Request").start(); >@Override public void run() < for (int i = 0; i < request.length; i++) < interaction.Request(request[i]); >> >
public class Response implements Runnable < ThreadInteraction interaction; String[] response = < "Aeroflot 1816, Dnipro Radar, go ahead", "Aeroflot 1816, descend to altitude 6,000 feet" >; public Response(ThreadInteraction interaction) < this.interaction = interaction; new Thread(this, "Response").start(); >@Override public void run() < for (int i = 0; i < response.length; i++) < interaction.Response(response[i]); >> >
public class ThreadInterActionDemo < public static void main(String[] args) < ThreadInteraction interaction = New ThreadInteraction(); Request request = New Request (interaction); Response response = New Response(interaction); >>

В результаті роботи цієї програми, ми отримаємо приблизно такий результат:

/*Some System Messages*/ Pilot: Dnipro Radar, Aeroflot 1816 Controller: Aeroflot 1816, Dnipro Radar, go ahead Pilot: Request descent, Aeroflot 1816 Controller: Aeroflot 1816, descend to altitude 6,006 1816

У цьому розділі ми вивчили основи взаємодії потоків і розглянули прикладні програми з його застосуванням.

Корисності

Трохи про себе

Вітаю! Мене звуть Євген. На цьому сайті я пишу про розробку програмного забезпечення. Зв'язатися зі мною ви можете по email: [email protected] Має сенс попередньо ознайомитися ось з цим FAQ розділом.

Останні публікації

Copyright © 2025 PROSELYTE.

Omega WordPress Theme by ThemeHall

Схожі статті

  • Як прибрати режим очікування на телефоні
  • Який метод потрібно викликати для запуску потоку
  • Який режим екрану краще для очей
  • Який із методів педагогічного дослідження Найпоширеніший
  • Який метод застосовується для згладжування графіків функцій
  • Який метод тестування не вимагає написання тестової документації
  • Який режим на кондиціонері холодний
  • Який матеріал застосовується під час моделювання вкладки прямим методом
  • Недавні статті

  • Як бродить зернова брага
  • Що робити якщо не засмагаєш на сонці чому засмага погано лягає на шкіру або перестає прилипати
  • Як швидко зняти гель лак без апарату
  • Як робиться Каті голови
  • Яка гребінець краще для об'єму
  • Чим роблять м'яку покрівлю
  • Чи можна залишати крем для обличчя на ніч
  • Де знаходиться датчик селектора
  • географія нашої діяльності
    вулиця Драгоманова, 27
    вул. Курчатова 1Б
    вул. Міцкевича 130
    вул. Лабунського, 1
    вул. Макарова-Пржевальського
    вул. Толстого 10
    вул. Грушевського 28
    вул. Перший промінь (Черняхівського)
    напишіть нам

    сообщение успешно отправлено
    x