JavaRush /Курсы /JAVA 25 SELF /Мьютексы и семафоры: синтаксис и задачи

Мьютексы и семафоры: синтаксис и задачи

JAVA 25 SELF
52 уровень , 3 лекция
Открыта

1. Мьютекс (Mutex): что это такое и как работает

Мьютекс (от английского «mutual exclusion» — «взаимное исключение») — механизм, который позволяет только одному потоку одновременно выполнять критическую секцию кода. Если мьютекс занят (захвачен другим потоком), остальные потоки ждут, пока он освободится.

В Java роль мьютекса часто выполняет объект, на котором синхронизируется код: synchronized. Начиная с 5-й версии Java, появился класс ReentrantLock — более явная и гибкая реализация мьютекса.

Схематично

Представьте комнату с единственным ключом (мьютексом). Чтобы войти, нужно взять ключ. Если ключа нет (его уже кто-то взял), вы ждёте у двери. Как только ключ возвращается на место (мьютекс освобождается), следующий человек может войти.

Синтаксис мьютекса в Java

Через synchronized (классика):

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }
}

Здесь весь метод increment защищён мьютексом — только один поток может выполнять его в данный момент.

Через ReentrantLock (более гибко):

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); // Захватываем мьютекс
        try {
            count++;
        } finally {
            lock.unlock(); // Обязательно освобождаем!
        }
    }
}

Важно! Всегда освобождайте мьютекс в блоке finally, иначе можете получить «вечную блокировку» (deadlock) — и программа зависнет.

Когда нужен мьютекс?

Мьютекс необходим, когда к ресурсу должен подходить только один поток за раз. Это может быть переменная, файл или база данных. Особенно важно использовать мьютекс, если работа с ресурсом не атомарна: даже простое count++ на самом деле состоит из трёх шагов — прочитать значение, увеличить и записать обратно. Без мьютекса несколько потоков могут вмешаться между шагами и вызвать гонку данных.

2. Семафор (Semaphore): зачем он нужен и как работает

Семафор — «регулятор», который разрешает одновременно работать с ресурсом нескольким потокам, но не более заданного количества. Если лимит исчерпан, остальные потоки ждут своей очереди.

Аналогия: парковка на 3 машины. Если все места заняты, вновь прибывшие ждут, пока кто-то не уедет.

Синтаксис семафора в Java

Для этого используется класс Semaphore из пакета java.util.concurrent:

import java.util.concurrent.Semaphore;

public class ParkingLot {
    private final Semaphore spots;

    public ParkingLot(int places) {
        this.spots = new Semaphore(places);
    }

    public void parkCar(String car) throws InterruptedException {
        spots.acquire(); // Пытаемся занять место (если нет — ждём)
        try {
            System.out.println(car + " припарковалась.");
            Thread.sleep(1000); // Машина стоит на парковке
        } finally {
            spots.release(); // Освобождаем место
            System.out.println(car + " уехала.");
        }
    }
}

Использование:

ParkingLot parking = new ParkingLot(3);

for (int i = 1; i <= 5; i++) {
    final String car = "Машина " + i;
    new Thread(() -> {
        try {
            parking.parkCar(car);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Результат: одновременно на парковке не будет больше трёх машин — остальные ждут.

Как работает семафор?

  • При создании семафора задаётся количество «разрешений» (permits).
  • Метод acquire() пытается взять разрешение: если есть свободное — поток проходит, если нет — ждёт.
  • Метод release() возвращает разрешение обратно.
  • Семафор с одним разрешением ведёт себя почти как мьютекс, но без «владельца».

3. Мьютекс и семафор: в чём разница?

Характеристика Мьютекс (Mutex) Семафор (Semaphore)
Количество потоков Только один Несколько (ограниченное число)
Применение Защита ресурса Ограничение доступа (например, пул)
API в Java
synchronized, Lock
Semaphore
Управление Обычно «владелец» Может освобождать любой поток
Типичный сценарий Общий счётчик, объект Пул соединений, парковка, лимит

- Мьютекс — для случаев, когда нужен эксклюзивный доступ.
- Семафор — когда можно пускать несколько, но не всех.

Аналогия: мьютекс — уборная с одной кабинкой; семафор — уборная с тремя кабинками.

4. Практические примеры задач

Пример 1: Мьютекс для защиты критической секции

Допустим, у нас есть общий банк, и несколько потоков переводят деньги между счетами. Операции должны быть атомарными.

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {
    private int balance;
    private final Lock lock = new ReentrantLock();

    public BankAccount(int initial) {
        this.balance = initial;
    }

    public void deposit(int amount) {
        lock.lock();
        try {
            balance += amount;
        } finally {
            lock.unlock();
        }
    }

    public void withdraw(int amount) {
        lock.lock();
        try {
            if (balance >= amount) {
                balance -= amount;
            }
        } finally {
            lock.unlock();
        }
    }

    public int getBalance() {
        return balance;
    }
}

Здесь любые операции с балансом защищены мьютексом, чтобы не возникло race condition.

Пример 2: Семафор для ограничения доступа

Сервер может одновременно обрабатывать только 2 клиента (например, из‑за лицензии).

import java.util.concurrent.Semaphore;

public class Server {
    private final Semaphore connections = new Semaphore(2);

    public void handleRequest(String client) throws InterruptedException {
        connections.acquire();
        try {
            System.out.println(client + " подключился к серверу.");
            Thread.sleep(2000); // Имитация обработки запроса
        } finally {
            connections.release();
            System.out.println(client + " отключился.");
        }
    }
}

Использование:

Server server = new Server();

for (int i = 1; i <= 5; i++) {
    final String client = "Клиент " + i;
    new Thread(() -> {
        try {
            server.handleRequest(client);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}

Результат: одновременно сервер обслуживает не более двух клиентов.

5. Особенности и нюансы использования

Мьютекс: всегда освобождай!
Очень важно не забывать вызывать unlock() (или выходить из синхронизированного блока) даже при исключениях. Используйте try-finally:

lock.lock();
try {
    // критическая секция
} finally {
    lock.unlock();
}

Если забыть — можно получить «вечную блокировку»; другие потоки будут ждать бесконечно.

Семафор: можно ли освободить чужой поток?
В отличие от мьютекса, release() может вызвать любой поток, даже тот, который не делал acquire(). Это иногда удобно, но легко ошибиться — соблюдайте дисциплину.

Semaphore с одним разрешением = мьютекс?
Почти. Но у семафора нет понятия «владельца»: любое освобождение увеличивает счётчик разрешений. У мьютекса же освобождать должен тот, кто захватил.

Не путайте семафор и пул
Семафор — это не пул объектов, а только «счётчик разрешений». Его часто используют для реализации пулов (например, пул соединений к БД), но сам по себе он ничего не хранит.

6. Типичные ошибки при работе с мьютексами и семафорами

Ошибка №1: Забыли вызвать unlock/release. Если вы захватили мьютекс или семафор, но не вызвали unlock() или release(), другие потоки могут зависнуть навсегда. Всегда используйте try-finally, чтобы гарантировать освобождение блокировки даже при исключениях.

Ошибка №2: Синхронизация на неправильном объекте. Если синхронизироваться на переменной, которая не общая для всех потоков (например, на локальной переменной или строковом литерале), синхронизация работать не будет.

Ошибка №3: Двойное освобождение. В случае с семафором: если вызвать release() больше раз, чем было acquire(), количество разрешений увеличится сверх лимита. Следите за балансом!

Ошибка №4: Использование семафора вместо мьютекса (или наоборот). Если нужен эксклюзивный доступ, используйте мьютекс (synchronized или Lock). Если нужно ограничить число одновременно работающих потоков — используйте Semaphore.

Ошибка №5: Долгое удержание блокировки. Чем дольше поток держит мьютекс или семафор, тем дольше другие ждут. Минимизируйте время работы внутри критической секции.

1
Задача
JAVA 25 SELF, 52 уровень, 3 лекция
Недоступна
Мониторинг критических событий 🚨
Мониторинг критических событий 🚨
1
Задача
JAVA 25 SELF, 52 уровень, 3 лекция
Недоступна
Управление доступом к взлётно-посадочным полосам ✈️
Управление доступом к взлётно-посадочным полосам ✈️
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ