ReadWriteLock w scenariuszach czytelnik-pisarz
Używaj ReadWriteLock, aby zezwalać na równoczesne odczyty i zapewniać wyłączność zapisu w pamięci podręcznej.
ReadWriteLock w scenariuszach czytelnik-pisarz to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.
Problem czytelników i pisarzy
Wiele wątków może bezpiecznie jednocześnie odczytywać współdzielone dane. Zapis wymaga jednak wyłącznego dostępu — nie mogą wtedy odbywać się równoczesne odczyty ani zapisy. ReadWriteLock modeluje tę zasadę: wiele jednoczesnych odczytów ALBO jeden wyłączny zapis.
import java.util.concurrent.locks.*;
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();Blokada odczytu: dostęp współdzielony
Wiele wątków może jednocześnie posiadać blokadę odczytu, o ile żaden wątek nie posiada blokady zapisu:
class ReadableCache {
private final Map<String, String> cache = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
String get(String key) {
lock.readLock().lock();
try {
return cache.get(key); // concurrent reads OK
} finally {
lock.readLock().unlock();
}
}
}Blokada zapisu: dostęp wyłączny
W danym momencie tylko jeden wątek może posiadać blokadę zapisu. Podczas zapisu wszyscy czytelnicy i pozostali autorzy zapisu są blokowani:
class WritableCache extends ReadableCache {
private final ReadWriteLock wLock = new ReentrantReadWriteLock();
private final Map<String, String> data = new HashMap<>();
void put(String key, String value) {
wLock.writeLock().lock();
try {
data.put(key, value); // exclusive write
} finally {
wLock.writeLock().unlock();
}
}
}Pełny przykład pamięci podręcznej
Bezpieczna dla wątków pamięć podręczna, w której odczyty dominują, a zapisy są rzadkie:
class Cache<K,V> {
private final Map<K,V> map = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
V get(K key) {
lock.readLock().lock();
try { return map.get(key); }
finally { lock.readLock().unlock(); }
}
void put(K key, V value) {
lock.writeLock().lock();
try { map.put(key, value); }
finally { lock.writeLock().unlock(); }
}
int size() {
lock.readLock().lock();
try { return map.size(); }
finally { lock.readLock().unlock(); }
}
}Obniżanie poziomu blokady
ReadWriteLock obsługuje obniżanie poziomu blokady: uzyskanie blokady zapisu → uzyskanie blokady odczytu → zwolnienie blokady zapisu. Pozwala to wątkowi przejść z wyłącznego zapisu do współdzielonego odczytu bez zwalniania blokady:
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Lock r = rwl.readLock(), w = rwl.writeLock();
w.lock(); // acquire write
try {
// modify data
r.lock(); // acquire read WHILE holding write
} finally {
w.unlock(); // release write, now holding read only
}
try {
// safely read the just-written data
} finally {
r.unlock();
}Blokada odczytu nie obsługuje podnoszenia poziomu
Podnoszenie poziomu blokady (odczyt → zapis) NIE jest obsługiwane. Jeśli wątek posiadający blokadę odczytu spróbuje uzyskać blokadę zapisu, dojdzie do zakleszczenia, ponieważ blokada zapisu czeka na WSZYSTKICH czytelników, w tym na ten wątek:
// DEADLOCK: Do NOT do this
lock.readLock().lock();
try {
lock.writeLock().lock(); // DEADLOCK — waits for readLock to release
} finally {
lock.readLock().unlock();
}Kiedy ReadWriteLock pomaga
ReadWriteLock jest korzystny, gdy:
- odczytów jest znacznie więcej niż zapisów
- operacje odczytu trwają zauważalnie długo, np. w przypadku złożonych zapytań
Może pogorszyć wydajność, gdy zapisy są częste (blokada zapisu blokuje wszystkich czytelników) albo gdy operacje są bardzo szybkie (narzut związany z dwiema blokadami).
Porównanie ReadWriteLock z synchronized
synchronized/ReentrantLock → tylko jeden wątek naraz (blokowane są nawet jednoczesne odczyty).
ReadWriteLock → wiele jednoczesnych odczytów i jeden wyłączny zapis. Lepsza przepustowość odczytów w scenariuszach z przewagą odczytów.
// synchronized: only one thread reads at a time
synchronized String get(String key) { return cache.get(key); }
// ReadWriteLock: many threads can read simultaneously
String get2(String key) {
lock.readLock().lock();
try { return cache.get(key); }
finally { lock.readLock().unlock(); }
}Aspekty wydajnościowe
Uzyskiwanie i zwalnianie dwóch blokad powoduje większy narzut niż użycie jednej blokady. Przed założeniem, że ReadWriteLock jest szybszy, należy wykonać profilowanie. W scenariuszach z przewagą odczytów i długimi operacjami korzyść jest wyraźna. W przypadku szybkich obciążeń z przewagą zapisów prosty ReentrantLock może być szybszy.
Alternatywa: ConcurrentHashMap
W przypadku prostych operacji na mapie ConcurrentHashMap jest często szybsza niż HashMap + ReadWriteLock, ponieważ używa wewnętrznych blokad na poziomie segmentów i nie występuje w niej globalna rywalizacja:
// Often better than HashMap + ReadWriteLock for simple operations:
Map<String, String> concurrent = new ConcurrentHashMap<>();
concurrent.put("key", "value"); // thread-safe, no explicit lock
String v = concurrent.get("key"); // thread-safeSprawiedliwość blokady odczytu
Domyślnie ReentrantReadWriteLock nie zapewnia sprawiedliwego dostępu (czytelnicy mogą zagłodzić autorów zapisu). Należy użyć konstruktora zapewniającego sprawiedliwy dostęp, aby zapobiec zagłodzeniu autorów zapisu:
// Fair: waiting writers are served before new readers
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
System.out.println(fairLock.isFair()); // trueSzybkie sprawdzenie
W aplikacji z przewagą odczytów jaka jest kluczowa zaleta ReadWriteLock w porównaniu z prostą metodą synchronized?
Podsumowanie: ReadWriteLock
Najważniejsze informacje:
- Wiele jednoczesnych odczytów ALBO jeden wyłączny zapis — nigdy oba jednocześnie
- readLock.lock()/unlock() do współdzielonych odczytów
- writeLock.lock()/unlock() do wyłącznych zapisów
- Obniżanie poziomu blokady jest obsługiwane; podnoszenie poziomu (odczyt→zapis) powoduje zakleszczenie
- Najlepszy wybór w przypadku przewagi odczytów i złożonych operacji; do prostych map należy użyć ConcurrentHashMap
Często zadawane pytania
Czy lekcja „ReadWriteLock w scenariuszach czytelnik-pisarz” jest bezpłatna?
Tak — pełny tekst „ReadWriteLock w scenariuszach czytelnik-pisarz” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „ReadWriteLock w scenariuszach czytelnik-pisarz”?
Używaj ReadWriteLock, aby zezwalać na równoczesne odczyty i zapewniać wyłączność zapisu w pamięci podręcznej. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Java Academy?
Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.
Ile czasu zajmuje lekcja „ReadWriteLock w scenariuszach czytelnik-pisarz”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?
Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- ReentrantLock a synchronized
- ReadWriteLock w scenariuszach czytelnik-pisarz
- Zmienne atomowe: aktualizacje bez blokad
- StampedLock i optymistyczne odczyty