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

Какие типы блокировок используются в многопоточности?

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

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

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

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

Типы блокировок в многопоточности Java

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

1. Intrinsic Locks (Встроенные блокировки)

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

Синхронизация методов

public class Counter {
    private int count = 0;
    
    // ✅ Синхронизированный метод
    public synchronized void increment() {
        count++;  // Защищён встроенной блокировкой объекта
    }
    
    public synchronized int getCount() {
        return count;
    }
}

// Использование
Counter counter = new Counter();
for (int i = 0; i < 1000; i++) {
    counter.increment();  // Потокобезопасно
}

Синхронизация блоков

public class SharedResource {
    private int data = 0;
    private Object lock = new Object();
    
    public void update() {
        // Критическая секция
        synchronized (lock) {
            data++;  // Защищена
            data *= 2;
        }
        
        // Некритичная секция
        System.out.println("Updated");  // Не защищена
    }
    
    public int getData() {
        synchronized (lock) {
            return data;  // Чтение также защищено
        }
    }
}

Проблемы встроенных блокировок

// ❌ Потенциальный deadlock
public class Account {
    private synchronized void transfer(Account other, int amount) {
        this.balance -= amount;
        other.receiveTransfer(amount);  // Другой объект блокируется!
        // Если другой Account ждёт этот, произойдёт deadlock
    }
}

// ❌ Нет timeout
synchronized (lock) {
    // Если другой поток не освобождает lock, ждём вечно
}

// ❌ Нет справедливости (fairness)
// Потоки могут конкурировать несправедливо

2. Explicit Locks (Lock interface)

Интерфейс Lock из пакета java.util.concurrent.locks предоставляет более гибкий контроль.

ReentrantLock

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

public class BankAccount {
    private double balance;
    private Lock lock = new ReentrantLock();  // ✅ Явная блокировка
    
    public void transfer(BankAccount other, double amount) {
        // Получаем блокировку с timeout
        try {
            if (lock.tryLock(1, TimeUnit.SECONDS)) {  // ✅ Timeout
                try {
                    if (other.lock.tryLock(1, TimeUnit.SECONDS)) {
                        try {
                            // Безопасная передача
                            this.balance -= amount;
                            other.balance += amount;
                        } finally {
                            other.lock.unlock();
                        }
                    } else {
                        System.out.println("Cannot acquire other lock");
                    }
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("Transfer timeout");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

ReadWriteLock

ReadWriteLock позволяет множественным потокам читать одновременно, но исключительный доступ для записи.

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CachedData {
    private String data;
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    
    // Много потоков могут читать одновременно
    public String read() {
        lock.readLock().lock();  // ✅ Неисключительная блокировка
        try {
            return data;
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // Только один поток может писать
    public void write(String newData) {
        lock.writeLock().lock();  // ✅ Исключительная блокировка
        try {
            data = newData;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// Использование
CachedData cache = new CachedData();

// Множество потоков читают параллельно
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
    executor.submit(() -> System.out.println(cache.read()));
}

// Писатель ждёт, пока все читатели закончат
executor.submit(() -> cache.write("New data"));

3. Atomics (Атомарные переменные)

Для простых операций с примитивами лучше использовать Atomic классы* без явных блокировок.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class AtomicCounter {
    // ✅ Потокобезопасно без synchronized
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // Атомарная операция
    }
    
    public int getCount() {
        return count.get();
    }
    
    public boolean compareAndSet(int expected, int newValue) {
        // ✅ CAS (Compare-And-Swap) операция
        return count.compareAndSet(expected, newValue);
    }
}

// Пример использования CAS
public class RetryableOperation {
    private AtomicInteger retryCount = new AtomicInteger(0);
    
    public void retryWithBackoff() {
        int attempt = 0;
        while (attempt < 3) {
            try {
                // Выполняем операцию
                if (retryCount.compareAndSet(attempt, attempt + 1)) {
                    System.out.println("Attempt " + (attempt + 1));
                    // Успешно увеличили счётчик
                    break;
                }
                attempt++;
            } catch (Exception e) {
                attempt++;
            }
        }
    }
}

4. StampedLock

StampedLock — оптимизированная версия ReadWriteLock с поддержкой оптимистического чтения.

import java.util.concurrent.locks.StampedLock;

public class OptimizedCache {
    private String data;
    private StampedLock lock = new StampedLock();
    
    // Оптимистическое чтение (очень быстро)
    public String readOptimistic() {
        long stamp = lock.tryOptimisticRead();  // ✅ Без блокировки
        String result = data;
        
        // Проверяем, не изменилось ли
        if (!lock.validate(stamp)) {
            // Если изменилось, переходим на обычное чтение
            stamp = lock.readLock();
            try {
                result = data;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        
        return result;
    }
    
    // Исключительная запись
    public void write(String newData) {
        long stamp = lock.writeLock();
        try {
            data = newData;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

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

Semaphore ограничивает количество потоков, имеющих доступ к ресурсу.

import java.util.concurrent.Semaphore;

public class ConnectionPool {
    private Semaphore semaphore;
    private List<Connection> connections;
    
    public ConnectionPool(int poolSize) {
        semaphore = new Semaphore(poolSize);  // Максимум poolSize потоков
        connections = new ArrayList<>();
        for (int i = 0; i < poolSize; i++) {
            connections.add(new Connection());
        }
    }
    
    public Connection acquireConnection() throws InterruptedException {
        semaphore.acquire();  // ✅ Ждём свободного слота
        synchronized (connections) {
            return connections.remove(0);
        }
    }
    
    public void releaseConnection(Connection conn) {
        synchronized (connections) {
            connections.add(conn);
        }
        semaphore.release();  // ✅ Освобождаем слот
    }
}

// Использование
ConnectionPool pool = new ConnectionPool(5);

ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    executor.submit(() -> {
        try {
            Connection conn = pool.acquireConnection();
            try {
                // Используем соединение
                System.out.println("Using connection");
                Thread.sleep(100);
            } finally {
                pool.releaseConnection(conn);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

6. CountDownLatch

CountDownLatch позволяет потокам ждать, пока другие потоки завершат свои задачи.

import java.util.concurrent.CountDownLatch;

public class RaceStarterExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(3);
        
        // 3 потока ждут сигнала старта
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    startSignal.await();  // ✅ Ждём сигнала
                    System.out.println(Thread.currentThread().getName() + " is running");
                    Thread.sleep(100);
                    doneSignal.countDown();  // ✅ Уменьшаем счётчик
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
        
        System.out.println("Starting race...");
        startSignal.countDown();  // ✅ Даём сигнал старта
        
        doneSignal.await();  // ✅ Ждём, пока все закончат
        System.out.println("Race finished");
    }
}

7. CyclicBarrier

CyclicBarrier — точка синхронизации, где потоки встречаются.

import java.util.concurrent.CyclicBarrier;

public class BarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(
            3,  // 3 потока должны встретиться
            () -> System.out.println("All threads reached barrier!")  // Колбэк
        );
        
        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                try {
                    System.out.println("Thread " + id + " is waiting");
                    barrier.await();  // ✅ Ждём других потоков
                    System.out.println("Thread " + id + " is proceeding");
                } catch (Exception e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

Сравнение типов блокировок

ТипСлучай использованияProsCons
SynchronizedПростая защитаВстроённо, простоНет timeout, нет fairness
ReentrantLockСложные сценарииTimeout, fairness, гибкостьНужно unlock в finally
ReadWriteLockМного читателейПараллельное чтениеOverhead для письма
AtomicIntegerПростые числаБыстро, без явных lockТолько примитивы
StampedLockОптимизм важенОчень быстроСложнее в использовании
SemaphoreОграничение потоковЛегко контролироватьНужна ручная работа
CountDownLatchСинхронизация стартаПростая координацияОдноразовая
CyclicBarrierВстречи потоковПереиспользуемаяБолее сложная

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

// ✅ Предпочитай AtomicInteger для простых счётчиков
private AtomicInteger counter = new AtomicInteger(0);

// ✅ Используй ReentrantLock для сложной логики
private Lock lock = new ReentrantLock();
lock.lock();
try {
    // Критический код
} finally {
    lock.unlock();  // Обязательно в finally!
}

// ✅ ReadWriteLock когда много читателей
private ReadWriteLock rwLock = new ReentrantReadWriteLock();

// ❌ Избегай вложенных блокировок
synchronized (a) {
    synchronized (b) {  // Риск deadlock
    }
}

// ❌ Не блокируй во время I/O
synchronized (this) {
    httpClient.get(url);  // Плохо: другие потоки ждут I/O
}

Заключение

Java предоставляет богатый набор инструментов для синхронизации:

  1. Synchronized — для простых случаев
  2. ReentrantLock — для control + timeout
  3. ReadWriteLock — для read-heavy scenarios
  4. Atomics — для примитивных типов
  5. Semaphore/CountDownLatch/CyclicBarrier — для координации потоков

Выбор правильного механизма зависит от требований вашей задачи.

Какие типы блокировок используются в многопоточности? | PrepBro