Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте Блокування та Умова | Механізми Синхронізації Високого Рівня
Багатопотоковість у Java

bookБлокування та Умова

У Java стандартна синхронізація базується на ключовому слові synchronized та вбудованих монітор-об'єктах. Проте в окремих випадках synchronized може бути недостатньо, особливо коли потрібна більша гнучкість у керуванні потоками.

Загальний опис

Інтерфейси Lock та Condition, запроваджені у пакеті java.util.concurrent.locks, надають розширені можливості для керування потоками.

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

Відмінність

Відмінність між цими двома інтерфейсами полягає в тому, що реалізації Lock є високорівневою альтернативою синхронізованому блоку, а реалізації інтерфейсу Condition є альтернативою методам notify()/wait(). Обидва ці інтерфейси входять до складу пакету java.util.concurrent.locks.

Приклади з реального життя

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

Клас ReentrantLock

Клас ReentrantLock з пакету java.util.concurrent.locks є реалізацією інтерфейсу Lock. Він надає функціональність для явного керування блокуваннями.

Основні методи ReentrantLock:

  • lock(): Захоплює блокування;
  • unlock(): Звільняє блокування;
  • tryLock(): Намагається захопити блокування та повертає true, якщо захоплення вдалося;
  • tryLock(long timeout, TimeUnit unit): Намагається захопити блокування протягом вказаного часу;
  • newCondition(): Створює умову для поточного Lock.
Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }

Як ви можете бачити, у методі increment() ми використовуємо блокування за допомогою Lock. Коли потік входить у метод, він захоплює блокування через lock.lock(), виконує код, а потім у блоці finally звільняє блокування через lock.unlock(), щоб інші потоки могли увійти.

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

Інтерфейс Condition

Ми можемо створити Condition лише з прив'язкою до конкретної реалізації Lock. З цієї причини методи Condition впливають лише на блокування саме цієї реалізації Lock.

Main.java

Main.java

copy
12
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();

Основні методи Condition:

  • await(): Очікування сигналу від іншого потоку;
  • signal(): Розблокування одного потоку, що очікує на умову;
  • signalAll(): Розблокування всіх потоків, які очікують на умову.
Main.java

Main.java

copy
12345678910111213141516
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }

Метод waitForCondition() блокує потік до тих пір, поки змінна ready не стане true, що сигналізує про виконання умови. Коли умова виконана, потік продовжує виконання, виводячи повідомлення “Condition met!”

Note
Примітка

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

Приклад коду

Тепер розглянемо приклад використання ReentrantLock та Condition для керування реєстрацією на подію:

Короткий фрагмент з відео

Блокування за допомогою ReentrantLock: Метод register() захоплює блокування через lock.lock(), щоб запобігти одночасному виконанню коду кількома потоками.

Condition з Condition: Якщо немає вільних місць, потік викликає spaceAvailable.await(), щоб чекати, поки не з'явиться місце.

Звільнення блокування: Коли потік звільняє місце за допомогою методу cancel(), він викликає spaceAvailable.signalAll(), щоб сповістити всі очікуючі потоки.

Обробка виключень: Використання блоків try-finally гарантує, що блокування буде знято навіть у разі виникнення виключення.

Note
Примітка

Використання Lock та Condition у Java забезпечує більш гнучке керування потоками та синхронізацією, ніж традиційний механізм synchronized. Це особливо корисно у складних сценаріях, коли потрібен більш точний контроль над потоками та умовами очікування.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 1

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Awesome!

Completion rate improved to 3.33

bookБлокування та Умова

Свайпніть щоб показати меню

У Java стандартна синхронізація базується на ключовому слові synchronized та вбудованих монітор-об'єктах. Проте в окремих випадках synchronized може бути недостатньо, особливо коли потрібна більша гнучкість у керуванні потоками.

Загальний опис

Інтерфейси Lock та Condition, запроваджені у пакеті java.util.concurrent.locks, надають розширені можливості для керування потоками.

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

Відмінність

Відмінність між цими двома інтерфейсами полягає в тому, що реалізації Lock є високорівневою альтернативою синхронізованому блоку, а реалізації інтерфейсу Condition є альтернативою методам notify()/wait(). Обидва ці інтерфейси входять до складу пакету java.util.concurrent.locks.

Приклади з реального життя

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

Клас ReentrantLock

Клас ReentrantLock з пакету java.util.concurrent.locks є реалізацією інтерфейсу Lock. Він надає функціональність для явного керування блокуваннями.

Основні методи ReentrantLock:

  • lock(): Захоплює блокування;
  • unlock(): Звільняє блокування;
  • tryLock(): Намагається захопити блокування та повертає true, якщо захоплення вдалося;
  • tryLock(long timeout, TimeUnit unit): Намагається захопити блокування протягом вказаного часу;
  • newCondition(): Створює умову для поточного Lock.
Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031
package com.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Main { // Creating a ReentrantLock object private final Lock lock = new ReentrantLock(); private int count = 0; // Method to increment the `count` variable public void increment() { lock.lock(); // Acquiring the `lock` try { count++; System.out.println("Count incremented to: " + count); } finally { lock.unlock(); // Releasing the `lock` } } public static void main(String[] args) { Main example = new Main(); // Runnable task to call the increment method Runnable task = example::increment; // Starting multiple threads to execute the task for (int i = 0; i < 5; i++) { new Thread(task).start(); } } }

Як ви можете бачити, у методі increment() ми використовуємо блокування за допомогою Lock. Коли потік входить у метод, він захоплює блокування через lock.lock(), виконує код, а потім у блоці finally звільняє блокування через lock.unlock(), щоб інші потоки могли увійти.

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

Інтерфейс Condition

Ми можемо створити Condition лише з прив'язкою до конкретної реалізації Lock. З цієї причини методи Condition впливають лише на блокування саме цієї реалізації Lock.

Main.java

Main.java

copy
12
private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition();

Основні методи Condition:

  • await(): Очікування сигналу від іншого потоку;
  • signal(): Розблокування одного потоку, що очікує на умову;
  • signalAll(): Розблокування всіх потоків, які очікують на умову.
Main.java

Main.java

copy
12345678910111213141516
private final ReentrantLock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean ready = false; public void waitForCondition() throws InterruptedException { lock.lock(); // Acquire the `lock` try { while (!ready) { // Check if the condition is not met System.out.println("Waiting..."); // Print a waiting message condition.await(); // Wait for the condition to be signaled } System.out.println("Condition met!"); // Print a message when the condition is met } finally { lock.unlock(); // Release the `lock` } }

Метод waitForCondition() блокує потік до тих пір, поки змінна ready не стане true, що сигналізує про виконання умови. Коли умова виконана, потік продовжує виконання, виводячи повідомлення “Condition met!”

Note
Примітка

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

Приклад коду

Тепер розглянемо приклад використання ReentrantLock та Condition для керування реєстрацією на подію:

Короткий фрагмент з відео

Блокування за допомогою ReentrantLock: Метод register() захоплює блокування через lock.lock(), щоб запобігти одночасному виконанню коду кількома потоками.

Condition з Condition: Якщо немає вільних місць, потік викликає spaceAvailable.await(), щоб чекати, поки не з'явиться місце.

Звільнення блокування: Коли потік звільняє місце за допомогою методу cancel(), він викликає spaceAvailable.signalAll(), щоб сповістити всі очікуючі потоки.

Обробка виключень: Використання блоків try-finally гарантує, що блокування буде знято навіть у разі виникнення виключення.

Note
Примітка

Використання Lock та Condition у Java забезпечує більш гнучке керування потоками та синхронізацією, ніж традиційний механізм synchronized. Це особливо корисно у складних сценаріях, коли потрібен більш точний контроль над потоками та умовами очікування.

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 1
some-alt