0Pricing
Java Academy · Lekcja

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-safe

Sprawiedliwość 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()); // true

Szybkie 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

  1. ReentrantLock a synchronized
  2. ReadWriteLock w scenariuszach czytelnik-pisarz
  3. Zmienne atomowe: aktualizacje bez blokad
  4. StampedLock i optymistyczne odczyty
← Powrót do Java Academy