← Назад к вопросам

Что такое блокировки?

2.0 Middle🔥 161 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Блокировки (Locks) в Java

Блокировки — это синхронизационные механизмы, которые управляют доступом нескольких потоков к общим ресурсам. Они предотвращают race conditions, когда несколько потоков пытаются одновременно изменять одни данные.

Концепция блокировки

Без блокировок:

public class Counter {
    private int value = 0;
    
    public void increment() {
        value++;  // ПРОБЛЕМА: race condition!
        // Поток 1 читает 5
        // Поток 2 читает 5
        // Оба пишут 6
        // Результат: value = 6 вместо 7
    }
}

С блокировкой:

public class Counter {
    private int value = 0;
    private final Object lock = new Object();
    
    public void increment() {
        synchronized(lock) {  // одновременно только один поток
            value++;
        }
    }
}

Типы блокировок

1. Synchronized (Мониторы)

Встроенный механизм Java, простейший вид блокировки.

public synchronized void method() {
    // Заблокирован весь метод
    // Только один поток за раз может выполнять этот метод
}

Эквивалентно:

public void method() {
    synchronized(this) {
        // блокировка на объекте this
    }
}

Проблема synchronized:

  • Не можно выставить timeout
  • Не можно отменить
  • Нет условных переменных (условное ожидание)

2. ReentrantLock (Явная блокировка)

Из java.util.concurrent.locks, более гибкая.

public class Counter {
    private int value = 0;
    private final ReentrantLock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            value++;
        } finally {
            lock.unlock();  // ВАЖНО: всегда в finally
        }
    }
    
    public boolean tryIncrement() {
        if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                value++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;  // не удалось заблокировать за 100ms
    }
}

Преимущества ReentrantLock:

  • Timeout поддержка
  • Может прерваться (interruption)
  • Условные переменные (Condition)
  • Reentrancy: один поток может заблокировать несколько раз

3. ReadWriteLock (Читающие/Пишущие блокировки)

Несколько потоков могут читать одновременно, но запись требует исключительный доступ.

public class UserRepository {
    private Map<Integer, User> users = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public User getUser(int id) {
        lock.readLock().lock();
        try {
            return users.get(id);  // много потоков могут читать
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void saveUser(User user) {
        lock.writeLock().lock();
        try {
            users.put(user.getId(), user);  // только один поток может писать
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Когда использовать: много читателей, редкие записи (например, кэш конфигурации).

4. Semaphore (Семафор)

Ограничивает количество потоков, получивших доступ к ресурсу.

public class ConnectionPool {
    private final Semaphore semaphore = new Semaphore(10);  // максимум 10 соединений
    
    public Connection getConnection() throws InterruptedException {
        semaphore.acquire();  // занять место
        try {
            return createConnection();
        } finally {
            semaphore.release();  // освободить место
        }
    }
}

5. CountDownLatch

Один поток ждёт, пока N других потоков завершат свою работу.

public class DownloadManager {
    public static void main(String[] args) throws InterruptedException {
        int fileCount = 5;
        CountDownLatch latch = new CountDownLatch(fileCount);
        
        for (int i = 0; i < fileCount; i++) {
            new Thread(() -> {
                try {
                    System.out.println("Загрузка файла..." + Thread.currentThread().getName());
                    Thread.sleep(2000);
                } finally {
                    latch.countDown();  // уменьшить счётчик
                }
            }).start();
        }
        
        latch.await();  // ждать, пока счётчик не будет 0
        System.out.println("Все файлы загружены!");
    }
}

6. CyclicBarrier

N потоков ждут друг друга до определённой точки.

CyclicBarrier barrier = new CyclicBarrier(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " подошёл к барьеру");
        try {
            barrier.await();  // ждать, пока все три потока не подойдут
        } catch (Exception e) {}
        System.out.println(Thread.currentThread().getName() + " прошёл барьер");
    }).start();
}

// Выведет:
// Thread-0 подошёл к барьеру
// Thread-1 подошёл к барьеру
// Thread-2 подошёл к барьеру
// Все три потока одновременно пройдут дальше

Deadlock (Взаимная блокировка)

Два потока ждут друг друга:

public class DeadlockExample {
    Object lock1 = new Object();
    Object lock2 = new Object();
    
    public void method1() {
        synchronized(lock1) {
            synchronized(lock2) {  // ждёт lock2
                // code
            }
        }
    }
    
    public void method2() {
        synchronized(lock2) {
            synchronized(lock1) {  // ждёт lock1
                // code
            }
        }
    }
}

Условия deadlock:

  1. Несколько ресурсов
  2. Потоки держат один и ждут другой
  3. Циклическая зависимость
  4. Невозможно отнять ресурс

Предотвращение:

  • Всегда блокировать в одном порядке
  • Использовать timeout
  • Избегать вложенных блокировок

Volatile переменная

Лёгкая альтернатива блокировке для флагов:

public class Task implements Runnable {
    private volatile boolean running = true;  // видима всем потокам
    
    public void stop() {
        running = false;  // все потоки видят изменение
    }
    
    @Override
    public void run() {
        while (running) {
            // работа
        }
    }
}

volatile НЕ заменяет блокировку для complex операций.

Практический пример: Thread-safe счётчик

public class ThreadSafeCounter {
    private final AtomicInteger value = new AtomicInteger(0);
    
    public void increment() {
        value.incrementAndGet();  // атомарная операция
    }
    
    public int getValue() {
        return value.get();
    }
}

AtomicInteger — встроенный класс для простых атомарных операций, лучше чем ReentrantLock.

Лучшие практики

  1. Используй ReentrantLock вместо synchronized для сложных сценариев
  2. Не вкладывай блокировки — риск deadlock
  3. Всегда освобождай в finally — даже если исключение
  4. Используй concurrent. классы* — ConcurrentHashMap, CopyOnWriteArrayList
  5. Избегай блокировок, где возможно — используй immutable объекты
  6. Профилируй contention — узкие места с блокировками

Вывод: Блокировки критичны для многопоточности. Неправильное использование приводит к race conditions и deadlocks. Используй современные инструменты из java.util.concurrent.