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

Что такое ReadWriteLock?

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

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

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

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

# ReadWriteLock в Java

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

1. Проблема, которую решает ReadWriteLock

Обычный Lock (ReentrantLock)

public class UserCache {
    private Map<Long, User> cache = new HashMap<>();
    private final Lock lock = new ReentrantLock();
    
    // Чтение
    public User getUser(Long id) {
        lock.lock(); // Блокируем поток для ВСЕХ операций
        try {
            return cache.get(id);
        } finally {
            lock.unlock();
        }
    }
    
    // Запись
    public void putUser(Long id, User user) {
        lock.lock(); // Блокируем поток
        try {
            cache.put(id, user);
        } finally {
            lock.unlock();
        }
    }
}

Проблема: если 100 потоков читают из кэша одновременно, они будут ждать друг друга, хотя читают безопасно (не конфликтуют). Это неэффективно.

Решение: ReadWriteLock

public class UserCache {
    private Map<Long, User> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    // Чтение — множество потоков может читать одновременно
    public User getUser(Long id) {
        lock.readLock().lock();
        try {
            return cache.get(id);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    // Запись — исключительный доступ
    public void putUser(Long id, User user) {
        lock.writeLock().lock();
        try {
            cache.put(id, user);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Результат: 100 потоков читают одновременно без блокировок, а запись ждёт завершения всех читателей.

2. Как работает ReadWriteLock

Операция     | Read Lock | Write Lock |
           Можно?         Можно?
─────────────┼───────────┼────────────
Читать       | ДА (*)    | НЕТ
Писать       | НЕТ       | ДА (когда никто не читает)

(*) Множество потоков одновременно

Сценарии блокировок

Predicate<Boolean> readLockAcquired = false;
Predicate<Boolean> writeLockAcquired = false;

// Сценарий 1: Оба читают (РАЗРЕШЕНО)
// Поток 1: read lock
// Поток 2: read lock ✓ успешно

// Сценарий 2: Один читает, другой пишет (ЗАПРЕЩЕНО)
// Поток 1: read lock
// Поток 2: write lock ✗ БЛОКИРУЕТСЯ до завершения чтения

// Сценарий 3: Один пишет, другой читает (ЗАПРЕЩЕНО)
// Поток 1: write lock
// Поток 2: read lock ✗ БЛОКИРУЕТСЯ до завершения записи

// Сценарий 4: Оба пишут (ЗАПРЕЩЕНО)
// Поток 1: write lock
// Поток 2: write lock ✗ БЛОКИРУЕТСЯ

3. Интерфейс ReadWriteLock

public interface ReadWriteLock {
    Lock readLock();  // Для чтения
    Lock writeLock(); // Для записи
}

// Основная реализация
public class ReentrantReadWriteLock implements ReadWriteLock {
    // Конструкторы
    public ReentrantReadWriteLock() { }
    public ReentrantReadWriteLock(boolean fair) { } // fair — справедливое распределение
}

4. Практические примеры

Кэш пользователей

public class UserCache {
    private final Map<Long, User> cache = new ConcurrentHashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public Optional<User> get(Long id) {
        lock.readLock().lock();
        try {
            return Optional.ofNullable(cache.get(id));
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void put(Long id, User user) {
        lock.writeLock().lock();
        try {
            cache.put(id, user);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public void remove(Long id) {
        lock.writeLock().lock();
        try {
            cache.remove(id);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

Счётчик конфигураций с редким обновлением

public class ConfigurationManager {
    private Map<String, String> config = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    // Часто читается, редко пишется
    public String getProperty(String key) {
        lock.readLock().lock();
        try {
            return config.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }
    
    public void updateProperty(String key, String value) {
        lock.writeLock().lock();
        try {
            config.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public Map<String, String> getAllProperties() {
        lock.readLock().lock();
        try {
            return new HashMap<>(config); // Копия для безопасности
        } finally {
            lock.readLock().unlock();
        }
    }
}

Счётчик просмотров (много чтений, редкие обновления)

public class ViewCounter {
    private long viewCount = 0;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public void incrementView() {
        lock.writeLock().lock();
        try {
            viewCount++;
        } finally {
            lock.writeLock().unlock();
        }
    }
    
    public long getViewCount() {
        lock.readLock().lock();
        try {
            return viewCount;
        } finally {
            lock.readLock().unlock();
        }
    }
}

5. Оптимизация с try-with-resources (Java 9+)

import java.util.concurrent.locks.StampedLock;

public class OptimizedCache {
    private Map<Long, User> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    public User getUser(Long id) {
        Lock readLock = lock.readLock();
        readLock.lock();
        try {
            return cache.get(id);
        } finally {
            readLock.unlock();
        }
    }
}

6. StampedLock — ещё лучше

import java.util.concurrent.locks.StampedLock;

public class OptimizedUserCache {
    private Map<Long, User> cache = new HashMap<>();
    private final StampedLock lock = new StampedLock();
    
    public User getUser(Long id) {
        // Оптимистичное чтение (без блокировки)
        long stamp = lock.tryOptimisticRead();
        User user = cache.get(id);
        
        // Если данные могли измениться, повторяем с блокировкой
        if (!lock.validate(stamp)) {
            stamp = lock.readLock();
            try {
                user = cache.get(id);
            } finally {
                lock.unlockRead(stamp);
            }
        }
        return user;
    }
    
    public void putUser(Long id, User user) {
        long stamp = lock.writeLock();
        try {
            cache.put(id, user);
        } finally {
            lock.unlockWrite(stamp);
        }
    }
}

Преимущество StampedLock:

  • Оптимистичное чтение без блокировки
  • Лучше производительность на высоконагруженных системах
  • Меньше context switch'ей

7. Когда использовать ReadWriteLock

СценарийReadWriteLock?Альтернатива
Много чтений, мало записейДАsynchronized
Равномерно чтение и записьНЕТsynchronized, ReentrantLock
Только записьНЕТReentrantLock
Требуется атомарностьНЕТAtomicInteger, AtomicReference
Горячее чтениеНЕТStampedLock, ConcurrentHashMap

8. Потенциальные проблемы

Deadlock

// ОПАСНО: может привести к deadlock
public void transfer(User from, User to) {
    readLock1.lock();  // Берём читающую блокировку на from
    writeLock2.lock(); // Берём писающую блокировку на to
    // ...
}

Следите за порядком

// ХОРОШО: всегда в одном порядке
public void transfer(User from, User to) {
    if (from.getId() < to.getId()) {
        writeLock1.lock();
        writeLock2.lock();
    } else {
        writeLock2.lock();
        writeLock1.lock();
    }
    // ...
}

Заключение

ReadWriteLock — это оптимизация для сценариев, где операции чтения значительно превышают операции записи. Его использование позволяет повысить параллелизм и производительность. В Java 8+ предпочтение отдаётся StampedLock для критичных по производительности систем.