Взаємодія потоків. Методи wait та notify. Семафори
Взаємодія потоків. Методи wait та notify. Семафори – java курс
Взаємодія між потоками в багатопоточних програмах важлива, оскільки потоки можуть залежати один від одного. Наприклад, один потік може чекати, поки інший виконає якусь дію. У Java для організації взаємодії між потоками використовуються методи `wait()`, `notify()`, `notifyAll()` і семафори.
Методи `wait()` і `notify()`
Ці методи дозволяють організовувати взаємодію між потоками через **монітор** об’єкта. Кожен об’єкт в Java має власний монітор, і методи `wait()`, `notify()` і `notifyAll()` дозволяють одному потоку призупинити виконання, поки інший не повідомить, що можна продовжувати.
Як працюють ці методи:
- wait(): Викликається потоком на об’єкті, змушуючи цей потік чекати, поки інший потік не викличе метод `notify()` або `notifyAll()` на цьому ж об’єкті.
- notify(): Викликається потоком для пробудження одного з потоків, що чекає на цьому об’єкті.
- notifyAll(): Пробуджує всі потоки, що чекають на об’єкті.
Методи `wait()`, `notify()`, `notifyAll()` можуть бути викликані тільки всередині **синхронізованого блоку** або методу.
Приклад:
class Message {
private String message;
public synchronized void send(String msg) {
this.message = msg;
notify(); // Повідомляє потік, що дані готові
}
public synchronized String receive() throws InterruptedException {
wait(); // Чекає, поки інший потік не відправить повідомлення
return message;
}
}
public class Main {
public static void main(String[] args) {
Message message = new Message();
// Потік для отримання повідомлення
Thread receiver = new Thread(() -> {
try {
String receivedMessage = message.receive();
System.out.println(“Отримано повідомлення: ” + receivedMessage);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// Потік для відправки повідомлення
Thread sender = new Thread(() -> {
try {
Thread.sleep(1000); // Затримка перед відправкою
message.send(“Привіт, це потік відправника!”);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
receiver.start();
sender.start();
}
}
У цьому прикладі потік **receiver** чекає за допомогою `wait()`, поки **sender** не викличе метод `notify()`, щоб повідомити про наявність даних для обробки.
Основні моменти при використанні `wait()` і `notify()`:
- Синхронізація: Методи `wait()`, `notify()` і `notifyAll()` можуть бути викликані тільки всередині синхронізованого блоку або методу, інакше виникає виключення `IllegalMonitorStateException`.
- wait(): Зупиняє потік і передає монітор об’єкта, дозволяючи іншим потокам працювати з цим об’єктом.
- notify(): Пробуджує один із потоків, який чекає на моніторі об’єкта, але не гарантує, який саме потік буде пробуджений.
- notifyAll(): Пробуджує всі потоки, що чекають на моніторі об’єкта, але тільки один з них отримає управління після завершення синхронізованого блоку.
Семафори
Семафор (semaphore) — це механізм синхронізації, який обмежує кількість потоків, що можуть одночасно мати доступ до певного ресурсу. На відміну від `synchronized`, семафор дозволяє обмежити не тільки один потік, але й декілька (в залежності від кількості дозволених потоків).
У Java семафори представлені класом `Semaphore`, який знаходиться в пакеті `java.util.concurrent`.
Основні методи класу `Semaphore`:
- `acquire()`: Зменшує лічильник дозволених потоків на 1. Якщо лічильник досяг нуля, потік блокується, поки не звільниться дозвіл.
- `release()`: Збільшує лічильник дозволених потоків, дозволяючи новому потоку отримати доступ до ресурсу.
Приклад використання семафора:
import java.util.concurrent.Semaphore;
class SharedResource {
private Semaphore semaphore = new Semaphore(1); // Дозволяє одному потоку доступ
public void accessResource(String threadName) {
try {
semaphore.acquire(); // Отримує дозвіл на доступ до ресурсу
System.out.println(threadName + ” отримав доступ до ресурсу.”);
Thread.sleep(2000); // Виконання дії
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadName + ” звільняє ресурс.”);
semaphore.release(); // Звільняє дозвіл
}
}
}
public class Main {
public static void main(String[] args) {
SharedResource sharedResource = new SharedResource();
Thread t1 = new Thread(() -> sharedResource.accessResource(“Потік 1”));
Thread t2 = new Thread(() -> sharedResource.accessResource(“Потік 2”));
Thread t3 = new Thread(() -> sharedResource.accessResource(“Потік 3”));
t1.start();
t2.start();
t3.start();
}
}
У цьому прикладі семафор з кількістю дозволів, рівною 1, дозволяє одночасний доступ тільки одному потоку до ресурсу. Інші потоки чекають, поки доступ не буде звільнено.
Типи семафорів:
- Бінарний семафор (Semaphore з кількістю дозволів = 1) працює подібно до блокування або мютексу, дозволяючи доступ лише одному потоку.
- Підрахунковий семафор дозволяє кільком потокам одночасно отримувати доступ до ресурсу, в залежності від кількості дозволів.
Висновок
- Методи `wait()` і `notify()` дозволяють потокам взаємодіяти через монітор об’єкта, передаючи контроль між потоками.
- Семафори використовуються для обмеження кількості потоків, які одночасно мають доступ до ресурсу.
- Використання `synchronized`, `wait()`, `notify()`, семафорів дозволяє ефективно синхронізувати потоки та запобігати виникненню конфліктів доступу до спільних ресурсів.