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

Какие знаешь виды Lock?

2.0 Middle🔥 221 комментариев
#Многопоточность

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

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

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

Типы блокировок (Locks) в Java

Blokirovki (Locks) - это механизм синхронизации потоков для защиты общих ресурсов от одновременного доступа. Java предоставляет различные типы локов с разными характеристиками и применением.

1. Intrinsic Lock (встроенная блокировка через synchronized)

Это самый базовый и исторический вид блокировки в Java:

public class IntrinsicLockExample {
    private int counter = 0;
    
    // Блокировка на уровне метода
    public synchronized void incrementCounter() {
        counter++;
    }
    
    // Блокировка на объекте
    public void criticalSection() {
        synchronized(this) {
            counter++;
        }
    }
    
    // Статическая блокировка (на класс)
    public synchronized static void staticMethod() {
        // блокировка Class объекта
    }
}

Характеристики:

  • Неограниченное время ожидания
  • Нет возможности проверить статус блокировки
  • Выходит автоматически при выходе из scope
  • Fair scheduling НЕ гарантирован

2. ReentrantLock - переиспользуемая блокировка

Это явная блокировка, которая может быть переиспользована одним потоком:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int counter = 0;
    
    public void increment() {
        lock.lock();  // получить блокировку
        try {
            counter++;
        } finally {
            lock.unlock();  // ВСЕГДА отпустить в finally
        }
    }
    
    // Один тот же поток может захватить несколько раз
    public void reentrantExample() {
        lock.lock();      // счётчик = 1
        try {
            lock.lock();  // счётчик = 2 (один поток может)
            try {
                // критическая секция
            } finally {
                lock.unlock();  // счётчик = 1
            }
        } finally {
            lock.unlock();  // счётчик = 0
        }
    }
    
    // Попытка захватить с timeout
    public boolean incrementWithTimeout() {
        try {
            if (lock.tryLock(java.util.concurrent.TimeUnit.SECONDS, 5)) {
                try {
                    counter++;
                    return true;
                } finally {
                    lock.unlock();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return false;
    }
    
    // Проверить если блокировка свободна
    public boolean incrementIfFree() {
        if (lock.tryLock()) {  // не блокирует, сразу возвращает
            try {
                counter++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
}

Преимущества над synchronized:

  • tryLock() - неблокирующая попытка
  • tryLock(timeout) - с таймаутом
  • lockInterruptibly() - может быть прервана
  • Более гибкое управление
  • Fair locking возможен

3. ReentrantReadWriteLock - блокировка для чтения/записи

Позволяет множеству потоков читать одновременно, но только одному писать:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private int value = 0;
    
    // Много потоков могут читать одновременно
    public int getValue() {
        lock.readLock().lock();
        try {
            return value;
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // Только один поток может писать
    public void setValue(int newValue) {
        lock.writeLock().lock();
        try {
            value = newValue;
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    // Downgrade из write lock в read lock
    public int updateAndGet() {
        lock.writeLock().lock();
        try {
            value++;
            // Было бы хорошо здесь downgrade, но ReentrantReadWriteLock не поддерживает
            return value;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// Use case: кэш, который часто читается но редко обновляется
public class CacheWithReadWriteLock<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    
    public V get(K key) {
        lock.readLock().lock();  // множество потоков могут читать
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(K key, V value) {
        lock.writeLock().lock();  // только один поток пишет
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

4. StampedLock - оптимистичная блокировка (Java 8+)

Оплата с оптимистичным чтением для максимальной производительности:

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    private int x = 0, y = 0;
    
    // Оптимистичное чтение - не захватывает блокировку
    public int readValues() {
        long stamp = lock.tryOptimisticRead();  // почти бесплатно
        
        int localX = x;  // читаем данные
        int localY = y;
        
        // Проверяем, не изменились ли данные во время чтения
        if (!lock.validate(stamp)) {
            // Данные менялись, нужно перечитать с настоящей блокировкой
            stamp = lock.readLock();  // захватываем read lock
            try {
                localX = x;
                localY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        
        return localX + localY;
    }
    
    // Эксклюзивное (exclusive) чтение и запись
    public void writeValues(int newX, int newY) {
        long stamp = lock.writeLock();  // эксклюзивная блокировка
        try {
            x = newX;
            y = newY;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
    
    // Upgrade из read в write
    public void incrementX() {
        long stamp = lock.readLock();
        try {
            while (true) {
                long writeStamp = lock.tryConvertToWriteLock(stamp);
                if (writeStamp != 0L) {
                    try {
                        x++;
                        stamp = writeStamp;
                        break;
                    } finally {
                        lock.unlockWrite(writeStamp);
                    }
                } else {
                    lock.unlockRead(stamp);
                    stamp = lock.writeLock();
                }
            }
        } finally {
            if (lock.isReadLocked(stamp)) {
                lock.unlockRead(stamp);
            }
        }
    }
}

Когда использовать:

  • Очень частые чтения, редкие записи
  • Данные читаются быстро (без вложенных вызовов)
  • Валидация успешна в большинстве случаев

5. Semaphore - семафор (ограничение доступа)

Ограничивает количество потоков, могущих одновременно получить доступ:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    // Только 3 потока могут одновременно использовать ресурс
    private final Semaphore semaphore = new Semaphore(3);
    
    public void accessLimitedResource() throws InterruptedException {
        semaphore.acquire();  // уменьшает счётчик
        try {
            // работаем с ограниченным ресурсом (например, connection pool)
            System.out.println("Accessing resource");
            Thread.sleep(1000);
        } finally {
            semaphore.release();  // увеличивает счётчик
        }
    }
    
    // Пример: ограничение connection pool
    public class ConnectionPool {
        private final Semaphore available = new Semaphore(10);  // 10 соединений
        private final Queue<Connection> pool = new ConcurrentLinkedQueue<>();
        
        public Connection getConnection() throws InterruptedException {
            available.acquire();
            Connection conn = pool.poll();
            return conn != null ? conn : createNewConnection();
        }
        
        public void returnConnection(Connection conn) {
            pool.offer(conn);
            available.release();
        }
    }
}

6. CountDownLatch - ожидание нескольких потоков

Ожидает завершения нескольких событий:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        // Ждём пока 3 потока завершат работу
        CountDownLatch latch = new CountDownLatch(3);
        
        // Запускаем 3 рабочих потока
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " started");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                latch.countDown();  // сигнализирует о завершении
                System.out.println(Thread.currentThread().getName() + " finished");
            }).start();
        }
        
        latch.await();  // блокируется до countDown == 0
        System.out.println("All threads finished");
    }
}

7. CyclicBarrier - синхронизация потоков на барьере

Все потоки ждут друг друга на точке синхронизации:

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3);
        
        for (int i = 0; i < 3; i++) {
            final int id = i;
            new Thread(() -> {
                try {
                    System.out.println("Thread " + id + " waiting at barrier");
                    barrier.await();  // ждёт пока все 3 потока сюда приходят
                    System.out.println("Thread " + id + " passed barrier");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Сравнительная таблица

Тип          | Переиспользуемость | Fair | Timeout | Use Case
---
synchronized | Нет                | Нет  | Нет     | Простые случаи
Reentrant    | Да                 | Да   | Да      | Гибкость нужна
ReadWrite    | Да                 | Да   | Да      | Много читает, редко пишет
Stamped      | Да                 | Нет  | Нет     | Очень много чтений
Semaphore    | -                  | -    | Да      | Ограничение доступа
CountDown    | -                  | -    | Да      | Ждать событий
Barrier      | Циклический        | -    | Да      | Синхронизация волн

Правила использования локов

  1. Всегда используй try-finally или try-with-resources
  2. Избегай вложенных локов (deadlock risk)
  3. Удерживай лок минимальное время
  4. Предпочитай ReentrantLock synchronized'у
  5. Для чтения/записи используй ReadWriteLock
  6. StampedLock только для очень частых чтений
Какие знаешь виды Lock? | PrepBro