Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# 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 для критичных по производительности систем.