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

Какие блокировки лучше по производительности

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

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

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

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

Какие блокировки лучше по производительности

В Java существует множество механизмов синхронизации. Выбор правильного — критичен для производительности многопоточного приложения.

1. synchronized — встроенная блокировка

// Самая простая, но не самая быстрая
public class SynchronizedExample {
    private int counter = 0;
    
    // Метод-блокировка: использует монитор объекта
    public synchronized void increment() {
        counter++;
    }
    
    // Блоки-блокировка
    public void incrementBlock() {
        synchronized (this) {
            counter++;
        }
    }
}

// Производительность: O(high) при high contention
// Плюсы:
// - Простой синтаксис
// - Автоматическое освобождение при исключении
// - Оптимизирован JVM (biased locking, lock coarsening)
// Минусы:
// - Блокирует весь объект
// - Грубая гранулярность

2. ReentrantLock — явная блокировка

import java.util.concurrent.locks.ReentrantLock;

// Более гибкая и быстрая чем synchronized
public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();
    private int counter = 0;
    
    public void increment() {
        lock.lock();
        try {
            counter++;
        } finally {
            lock.unlock();
        }
    }
    
    // Попытка захватить блокировку с timeout
    public boolean tryIncrement(long timeout) throws InterruptedException {
        if (lock.tryLock(timeout, TimeUnit.MILLISECONDS)) {
            try {
                counter++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;
    }
}

// Производительность: O(medium) при high contention
// Плюсы:
// - Быстрее чем synchronized при конкуренции
// - Поддерживает fairness
// - tryLock() для non-blocking
// Минусы:
// - Нужно вручную освобождать
// - Более сложный синтаксис

3. ReadWriteLock — блокировка читает/записи

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

// Идеально когда много читателей, мало писателей
public class ReadWriteLockExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private Map<String, String> cache = new HashMap<>();
    
    // Несколько потоков могут одновременно читать
    public String get(String key) {
        lock.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // Только один поток может писать
    public void put(String key, String value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

// Производительность: O(low) для reads, O(medium) для writes
// Плюсы:
// - Параллельное чтение
// - Отлично для кешей
// Минусы:
// - Медленнее чем ReentrantLock если много записей

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

import java.util.concurrent.locks.StampedLock;

// Самая быстрая для high read/low write сценариев
public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    private double x, y;
    
    // Оптимистичное чтение (очень быстро)
    public double distanceFromOrigin() {
        long stamp = lock.tryOptimisticRead();  // Не блокируем!
        double currentX = x;
        double currentY = y;
        
        // Проверяем, не изменились ли данные
        if (!lock.validate(stamp)) {
            // Данные изменились, повторяем с блокировкой
            stamp = lock.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                lock.unlockRead(stamp);
            }
        }
        
        return Math.hypot(currentX, currentY);
    }
    
    // Эксклюзивная запись
    public void move(double newX, double newY) {
        long stamp = lock.writeLock();
        try {
            x = newX;
            y = newY;
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

// Производительность: O(very low) для optimistic reads
// Плюсы:
// - Оптимистичное чтение без блокировки
// - Быстрее ReadWriteLock при много reads
// Минусы:
// - Более сложный синтаксис
// - Нужно иметь fallback

5. Atomic классы — lock-free синхронизация

import java.util.concurrent.atomic.*;

// Используют CAS (Compare-And-Swap) вместо блокировок
public class AtomicExample {
    // Для примитивов
    private AtomicInteger counter = new AtomicInteger(0);
    private AtomicLong timestamp = new AtomicLong(0);
    private AtomicBoolean flag = new AtomicBoolean(false);
    
    // Для объектов
    private AtomicReference<String> name = new AtomicReference<>("John");
    
    public void increment() {
        counter.incrementAndGet();  // Атомарная операция
    }
    
    public int getAndReset() {
        return counter.getAndSet(0);  // Свопируем значение
    }
}

// Производительность: O(very low) при low contention
// Плюсы:
// - Нет блокировок, нет deadlock'ов
// - Очень быстро при низкой конкуренции
// Минусы:
// - Только для простых операций
// - При высокой конкуренции медленнее блокировок

6. ConcurrentHashMap — специализированная структура

import java.util.concurrent.ConcurrentHashMap;

// Использует bucket-level лocking вместо таблица-level
public class ConcurrentHashMapExample {
    private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    
    public void update(String key) {
        // Только bucket, содержащий этот ключ, блокируется
        map.put(key, 1);
        
        // Атомарная операция
        map.computeIfPresent(key, (k, v) -> v + 1);
    }
}

// Производительность: O(low) для most operations
// Плюсы:
// - Лучше чем Collections.synchronizedMap()
// - Parallel streams support
// Минусы:
// - Не поддерживает итерацию без снимков

Сравнение производительности

Высокая конкуренция (многие потоки пишут):
1. Atomic* (очень быстро для простых операций)
2. ReentrantLock (быстро)
3. synchronized (JVM оптимизирует, может быть быстро)
4. ReadWriteLock (медленнее, так как много конкуренции)
5. StampedLock optimistic (медленнее, часто переходит в read lock)

Низкая конкуренция (мало потоков пишут, много читают):
1. StampedLock optimistic (очень быстро)
2. ReadWriteLock (быстро, много параллельного чтения)
3. Atomic* (быстро)
4. ReentrantLock (в порядке)
5. synchronized (в порядке, но меньше контроля)

Практический выбор

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

// Для коллекций
private Map<String, String> map = new ConcurrentHashMap<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());

// Для critical section с несколькими операциями
private final Object lock = new Object();
synchronized (lock) {
    // несколько операций
}

// Для read-heavy сценариев
private ReadWriteLock lock = new ReentrantReadWriteLock();

// Для очень read-heavy (Java 8+)
private StampedLock lock = new StampedLock();

Best Practices

  1. Используй Atomic для простых операций* — самые быстрые
  2. ConcurrentHashMap вместо synchronized Map — намного быстрее
  3. ReadWriteLock если много readers — параллельное чтение
  4. StampedLock если много readers и Java 8+ — оптимистичное чтение
  5. synchronized для простых critical sections — JVM оптимизирует хорошо
  6. Избегай вложенных блокировок — риск deadlock'ов
  7. Мониторь contention — используй profiler для анализа

Выбор правильной синхронизации может дать 10x улучшение производительности!

Какие блокировки лучше по производительности | PrepBro