Что такое мьютекс и как он реализован в Java?

Ответ

Мьютекс (Mutex, Mutual Exclusion) — это примитив синхронизации, который гарантирует, что только один поток может выполнять критическую секцию кода или иметь доступ к общему ресурсу в заданный момент времени. Его основная цель — предотвращение состояний гонки (race condition).

Реализации мьютекса в Java:

1. Встроенный мьютекс (Intrinsic Lock) через synchronized

Каждый объект в Java имеет встроенную блокировку (монитор), которая действует как мьютекс.

public class IntrinsicMutexExample {
    private int sharedCounter = 0;
    private final Object mutex = new Object(); // Произвольный объект как мьютекс

    public void increment() {
        // synchronized блок захватывает мьютекс объекта 'mutex'
        synchronized (mutex) {
            sharedCounter++; // Критическая секция защищена
            // Операция ++ не атомарна (read-modify-write), без мьютекса возможна потеря данных.
        }
    }

    // Синхронизированный метод использует мьютекс экземпляра 'this'
    public synchronized void decrement() {
        sharedCounter--;
    }
}

2. Явные мьютексы из java.util.concurrent.locks

Более гибкая альтернатива — интерфейс Lock и его реализация ReentrantLock.

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

public class ExplicitMutexExample {
    private int sharedCounter = 0;
    private final Lock mutex = new ReentrantLock(); // Явный мьютекс

    public void increment() {
        mutex.lock(); // Захват мьютекса
        try {
            sharedCounter++; // Критическая секция
        } finally {
            mutex.unlock(); // Освобождение мьютекса ВСЕГДА в finally-блоке!
        }
    }
}
Сравнение synchronized и ReentrantLock: Характеристика synchronized (встроенный) ReentrantLock (явный)
Реентерабельность Да Да
Прерываемое ожидание Нет Да (lockInterruptibly())
Попытка захвата с таймаутом Нет Да (tryLock(long time, TimeUnit unit))
Честность (fairness) Не гарантируется Можно задать в конструкторе (new ReentrantLock(true))
Условные переменные Один набор (wait/notify) на объект Множество (Condition) на один lock
Гибкость Базовая Высокая
Читаемость Выше Ниже (нужен try-finally)

Пример с tryLock() (избегание deadlock):

public boolean transferWithTimeout(Account from, Account to, int amount, long timeoutMs) throws InterruptedException {
    long stopTime = System.nanoTime() + timeoutMs * 1_000_000L;

    while (true) {
        if (from.getLock().tryLock()) {
            try {
                if (to.getLock().tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
                    try {
                        // Оба мьютекса захвачены
                        if (from.getBalance() >= amount) {
                            from.withdraw(amount);
                            to.deposit(amount);
                            return true;
                        }
                        return false;
                    } finally {
                        to.getLock().unlock();
                    }
                }
            } finally {
                from.getLock().unlock();
            }
        }
        if (System.nanoTime() > stopTime) return false;
        // Короткая пауза перед повторной попыткой
        Thread.sleep(10);
    }
}

Вывод: synchronized — это простой и безопасный выбор для большинства случаев. ReentrantLock следует использовать, когда необходима расширенная функциональность (таймауты, честность, условия).

Ответ 18+ 🔞

А, слушай, вот этот твой мьютекс, блядь... Это ж просто охреневшая штука, чтобы потоки не устроили бардак, как пьяные мужики в общей бане за последним полотенцем! Суть-то проще пареной репы: только один поток в данный момент может лезть в критическую секцию, а остальные пусть постоят, подождут, подумают о жизни, блядь.

Вот смотри, в Java это можно устроить двумя основными способами, и оба — рабочие, но с разным, понимаешь, колоритом.

1. Старый добрый synchronized — встроенная блокировка

Каждый объект в Java, блядь, с рождения имеет свой личный мьютекс, как встроенный чип. Используется элементарно, хоть в лоб.

public class IntrinsicMutexExample {
    private int sharedCounter = 0;
    private final Object mutex = new Object(); // Вот этот кирпич и будет нашим замком

    public void increment() {
        // synchronized блок — говорим: "Чувак, пока я тут, ни шагу дальше!"
        synchronized (mutex) {
            sharedCounter++; // Вот эта операция — та самая критическая секция
            // Без синхронизации тут был бы полный пиздец и потеря данных, потому что ++ — это не атомарно!
        }
    }

    // А можно и весь метод обернуть — тогда мьютексом будет сам объект 'this'
    public synchronized void decrement() {
        sharedCounter--;
    }
}

2. Взрослый, навороченный ReentrantLock из пакета java.util.concurrent.locks

А это уже, блядь, как швейцарский нож — возможностей овердохуища, но и ответственности больше.

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

public class ExplicitMutexExample {
    private int sharedCounter = 0;
    private final Lock mutex = new ReentrantLock(); // Явно создаём мьютекс

    public void increment() {
        mutex.lock(); // Жёстко захватываем
        try {
            sharedCounter++; // Делаем свои делишки
        } finally {
            mutex.unlock(); // ОБЯЗАТЕЛЬНО отпускаем в finally! Иначе deadlock — и все накрылись медным тазом.
        }
    }
}

Ну и чтобы ты понимал разницу, вот тебе сравнение, блядь, в лицо:

Особенность synchronized (простецкий) ReentrantLock (навороченный)
Реентерабельность Да, зайти можно повторно Да, тоже
Можно прервать ожидание Не-а, будешь спать, пока не разбудят Да, lockInterruptibly() — вежливо стукнем по плечу
Попытка захватить с таймаутом Ни хуя подобного Да, tryLock(5, TimeUnit.SECONDS) — "давай, у меня 5 секунд"
Честность (fairness) Как повезёт Можно включить в конструкторе (true) — очередь будет строгая
Условные переменные Один набор на объект (wait/notify) Много разных Condition на один lock — как отдельные комнаты ожидания
Гибкость Базовая, но надёжная Высокая, но можно и ногу себе прострелить
Читаемость Норм, всё в синтаксисе Ниже, потому что свой unlock не забудь, а то пиши пропало

Вот, например, красивый трюк с tryLock(), чтобы избежать мертвой хватки (deadlock), когда два потока держат друг другу яйца:

public boolean transferWithTimeout(Account from, Account to, int amount, long timeoutMs) throws InterruptedException {
    long stopTime = System.nanoTime() + timeoutMs * 1_000_000L;

    while (true) {
        if (from.getLock().tryLock()) {
            try {
                if (to.getLock().tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
                    try {
                        // Ура, оба мьютекса в кармане!
                        if (from.getBalance() >= amount) {
                            from.withdraw(amount);
                            to.deposit(amount);
                            return true;
                        }
                        return false;
                    } finally {
                        to.getLock().unlock();
                    }
                }
            } finally {
                from.getLock().unlock();
            }
        }
        if (System.nanoTime() > stopTime) return false;
        // Не получилось — спим немного и пробуем снова, не упрямься как баран, блядь.
        Thread.sleep(10);
    }
}

Итог, ёпта: если задача простая — бери synchronized, не прогадаешь. Если же нужно чтото хитровыебанное с таймаутами, честной очередью или кучей условий — тогда твой путь лежит к ReentrantLock. Главное — не забудь его отпустить, а то застрянешь в вечном ожидании, как Герасим у реки, блядь.