Ответ
Мьютекс (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. Главное — не забудь его отпустить, а то застрянешь в вечном ожидании, как Герасим у реки, блядь.