Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Мьютекс (Mutex)
Мьютекс (Mutual Exclusion) — это примитив синхронизации, который обеспечивает исключительный доступ к ресурсу. Только один поток может занимать мьютекс в один момент времени. Это предотвращает race conditions и обеспечивает потокобезопасность при работе с общими данными.
Концепция мьютекса
Мьютекс работает как замок:
Мьютекс свободен
↓
[Поток 1] берёт мьютекс → Мьютекс ЗАНЯТ
[Поток 2] ждёт → БЛОКИРОВАН
[Поток 3] ждёт → БЛОКИРОВАН
↓
[Поток 1] освобождает → Мьютекс СВОБОДЕН
↓
[Поток 2] берёт мьютекс → Мьютекс ЗАНЯТ
[Поток 1] ждёт → БЛОКИРОВАН
Мьютекс в Java: synchronized
В Java мьютекс реализован через ключевое слово synchronized:
public class MutexExample {
private int counter = 0;
// Способ 1: синхронизация метода
public synchronized void increment() {
counter++; // Критическая секция
}
// Способ 2: синхронизация блока
public void incrementBlock() {
synchronized (this) {
counter++; // Критическая секция
}
}
public synchronized int getCounter() {
return counter;
}
}
Синхронизация на методе
Когда вы используете synchronized на методе, блокируется весь объект:
public class SynchronizedMethod {
private int value = 0;
// Блокирует объект целиком
public synchronized void setValue(int v) {
this.value = v;
}
public synchronized int getValue() {
return this.value;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedMethod obj = new SynchronizedMethod();
// Поток 1: вызывает setValue
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.setValue(i);
}
});
// Поток 2: вызывает getValue
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println(obj.getValue());
}
});
// Только один может работать в раз — мьютекс!
t1.start();
t2.start();
t1.join();
t2.join();
}
}
Синхронизация блока (более гибко)
Можно синхронизировать только часть метода на разных объектах:
public class BlockSynchronization {
private int count1 = 0;
private int count2 = 0;
private Object lock1 = new Object(); // Отдельный мьютекс
private Object lock2 = new Object(); // Отдельный мьютекс
public void incrementCount1() {
// Синхронизация только count1
synchronized (lock1) {
count1++;
}
// Остальной код без синхронизации
doExpensiveOperation();
}
public void incrementCount2() {
// Синхронизация только count2
synchronized (lock2) {
count2++;
}
doExpensiveOperation();
}
private void doExpensiveOperation() {
// Может выполняться параллельно
}
public static void main(String[] args) throws InterruptedException {
BlockSynchronization obj = new BlockSynchronizedMethod();
// Эти операции работают параллельно!
// incrementCount1 не блокирует incrementCount2
Thread t1 = new Thread(() -> obj.incrementCount1());
Thread t2 = new Thread(() -> obj.incrementCount2());
t1.start();
t2.start();
t1.join();
t2.join();
}
}
Проблема: Race Condition без мьютекса
// ❌ ОПАСНО: без синхронизации
public class RaceCondition {
private int counter = 0;
public void increment() {
counter++; // НЕ атомарная операция!
// Фактически это: read → increment → write
}
public static void main(String[] args) throws InterruptedException {
RaceCondition obj = new RaceCondition();
// 2 потока по 1000 инкрементов
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
// Ожидаемый результат: 2000
// Фактический результат: например, 1823 (потеряны обновления)
System.out.println("Counter: " + obj.counter);
}
}
// ✅ ПРАВИЛЬНО: с мьютексом
public class SafeCounter {
private int counter = 0;
public synchronized void increment() {
counter++;
}
public static void main(String[] args) throws InterruptedException {
SafeCounter obj = new SafeCounter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
obj.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Counter: " + obj.counter); // Всегда 2000
}
}
Мьютекс для статических методов
public class StaticMutex {
private static int globalCounter = 0;
// Синхронизирует Class объект
public static synchronized void incrementGlobal() {
globalCounter++;
}
// Эквивалентно:
public static void incrementGlobalBlock() {
synchronized (StaticMutex.class) {
globalCounter++;
}
}
}
Реентрантные мьютексы (Reentrant Locks)
Один поток может несколько раз захватить один мьютекс:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantMutexExample {
private ReentrantLock lock = new ReentrantLock();
public void method1() {
lock.lock();
try {
System.out.println("In method1");
method2(); // Тот же поток может снова захватить!
} finally {
lock.unlock();
}
}
public void method2() {
lock.lock();
try {
System.out.println("In method2");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantMutexExample obj = new ReentrantMutexExample();
obj.method1(); // Работает без deadlock
}
}
Deadlock с мьютексами
Мьютексы могут привести к deadlock-у:
// ❌ ОПАСНО: deadlock
public class DeadlockExample {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
System.out.println("Thread holding lock1, trying to get lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) { // Может ждать lock2
System.out.println("Got both locks in method1");
}
}
}
public void method2() {
synchronized (lock2) {
System.out.println("Thread holding lock2, trying to get lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) { // Может ждать lock1
System.out.println("Got both locks in method2");
}
}
}
public static void main(String[] args) {
DeadlockExample obj = new DeadlockExample();
Thread t1 = new Thread(obj::method1);
Thread t2 = new Thread(obj::method2);
t1.start();
t2.start();
// DEADLOCK! Программа зависает
}
}
// ✅ ПРАВИЛЬНО: всегда в одном порядке
public class NoDeadlock {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
System.out.println("Got both in method1");
}
}
}
public void method2() {
// Всегда берём в одном порядке: lock1 → lock2
synchronized (lock1) {
synchronized (lock2) {
System.out.println("Got both in method2");
}
}
}
}
ReentrantLock vs synchronized
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
// ReentrantLock более гибкий
public class ReentrantLockAdvantages {
private ReentrantLock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private int value = 0;
// 1. Timeout при захвате
public boolean tryAcquire() throws InterruptedException {
return lock.tryLock(1, java.util.concurrent.TimeUnit.SECONDS);
}
// 2. Fairness (справедливость)
private ReentrantLock fairLock = new ReentrantLock(true);
// 3. Conditions для более гибкой синхронизации
public void waitUntilNotEmpty() throws InterruptedException {
lock.lock();
try {
while (value == 0) {
notEmpty.await(); // Ждёт сигнала
}
} finally {
lock.unlock();
}
}
public void signalNotEmpty() {
lock.lock();
try {
value = 1;
notEmpty.signalAll(); // Будит ждущие потоки
} finally {
lock.unlock();
}
}
}
Лучшие практики
- Минимизируй критическую секцию
public synchronized void badWay() {
synchronized (this) {
// Много кода
expensiveOperation();
anotherExpensiveOperation();
}
}
public void goodWay() {
expensiveOperation(); // Без синхронизации
anotherExpensiveOperation(); // Без синхронизации
synchronized (this) {
// Только обновление состояния
updateState();
}
}
- Используй finally или try-with-resources
lock.lock();
try {
// код
} finally {
lock.unlock(); // ОБЯЗАТЕЛЬНО!
}
- Избегай вложенных синхронизаций (риск deadlock)
- Документируй критические секции
- Используй утилиты (ConcurrentHashMap, AtomicInteger, и т.д.)
Заключение
Мьютекс — фундаментальный механизм для обеспечения потокобезопасности в многопоточных приложениях. В Java это реализовано через synchronized и ReentrantLock. Понимание мьютексов критично для:
- Предотвращения race conditions
- Гарантирования видимости изменений между потоками
- Избежания deadlock-ов
- Правильного проектирования потокобезопасного кода