読み取り・書き込み処理のための ReadWriteLock
キャッシュで読み取りの同時実行を許可しながら、書き込みを排他的に行うために ReadWriteLock を使用します。
「読み取り・書き込み処理のための ReadWriteLock」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。
リーダー・ライター問題
複数のスレッドは、共有データを安全に同時読み取りできます。一方、書き込みには排他的なアクセスが必要で、読み取りや書き込みを同時に行うことはできません。ReadWriteLockはこの状態をモデル化し、複数の読み取りスレッド、または1つの排他的な書き込みスレッドを許可します。
import java.util.concurrent.locks.*;
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();読み取りロック: 共有アクセス
書き込みロックを保持しているスレッドがない限り、複数のスレッドが同時に読み取りロックを保持できます:
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();
}
}
}書き込みロック: 排他的アクセス
一度に書き込みロックを保持できるスレッドは1つだけです。書き込み中は、すべての読み取りスレッドと他の書き込みスレッドがブロックされます:
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();
}
}
}完全なキャッシュの例
読み取りが多く、書き込みがまれなスレッドセーフなキャッシュです:
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(); }
}
}ロックのダウングレード
ReadWriteLockはロックのダウングレードに対応しています。書き込みロックを取得 → 読み取りロックを取得 → 書き込みロックを解放、という順序で操作します。これにより、ロックを完全に解放せずに、排他的な書き込みから共有読み取りへ移行できます:
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();
}読み取りロックはアップグレードできない
ロックのアップグレード(読み取り → 書き込み)には対応していません。読み取りロックを保持しているスレッドが書き込みロックを取得しようとすると、書き込みロックは自分自身を含むすべての読み取りスレッドが解放されるのを待つため、デッドロックが発生します:
// DEADLOCK: Do NOT do this
lock.readLock().lock();
try {
lock.writeLock().lock(); // DEADLOCK — waits for readLock to release
} finally {
lock.readLock().unlock();
}ReadWriteLockが役立つ場合
ReadWriteLockは、次のような場合に有効です:
- 書き込みより読み取りが大幅に多い場合
- 読み取り操作にある程度の時間がかかる場合(複雑なクエリなど)
書き込みが頻繁な場合(書き込みロックがすべての読み取りをブロックするため)や、操作が非常に高速な場合(2つのロックオブジェクトによるオーバーヘッドのため)は、パフォーマンスが低下する可能性があります。
ReadWriteLockとsynchronizedの比較
synchronized/ReentrantLock → 一度に1つのスレッドのみ(同時読み取りもブロックされます)。
ReadWriteLock → 複数の同時読み取りと、排他的な書き込み。読み取りが多い状況では、読み取りスループットが向上します。
// 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(); }
}パフォーマンスに関する考慮事項
2つのロックオブジェクトを取得・解放するため、1つのロックを使う場合よりオーバーヘッドが増えます。ReadWriteLockのほうが高速だと決めつける前に、プロファイリングしてください。読み取りが多く、操作に時間がかかる状況では、メリットが明確です。高速で書き込みが多いワークロードでは、シンプルなReentrantLockのほうが高速になる場合があります。
ConcurrentHashMapという選択肢
単純なマップ操作では、グローバルな競合がなく内部でセグメント単位のロックを使用するため、ConcurrentHashMapのほうがHashMap + ReadWriteLockより高速なことがよくあります:
// 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読み取りロックの公平性
デフォルトでは、ReentrantReadWriteLockは公平ではありません(読み取りスレッドによって書き込みスレッドが飢餓状態になる可能性があります)。公平なコンストラクターを使用して、書き込みスレッドの飢餓を防いでください:
// Fair: waiting writers are served before new readers
ReentrantReadWriteLock fairLock = new ReentrantReadWriteLock(true);
System.out.println(fairLock.isFair()); // trueクイックチェック
読み取りが多いアプリケーションで、単純なsynchronizedメソッドに対するReadWriteLockの主な利点は何ですか?
まとめ: ReadWriteLock
重要なポイント:
- 複数の同時読み取り、または1つの排他的な書き込み。両方を同時には実行できません
- 共有読み取りにはreadLock.lock()/unlock()を使用します
- 排他的な書き込みにはwriteLock.lock()/unlock()を使用します
- ロックのダウングレードには対応していますが、アップグレード(読み取り→書き込み)を行うとデッドロックが発生します
- 時間のかかる操作が多い読み取り中心の処理に最適です。単純なマップにはConcurrentHashMapを使用します
よくある質問
「読み取り・書き込み処理のための ReadWriteLock」レッスンは無料ですか?
はい。「読み取り・書き込み処理のための ReadWriteLock」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。
「読み取り・書き込み処理のための ReadWriteLock」で何を学びますか?
キャッシュで読み取りの同時実行を許可しながら、書き込みを排他的に行うために ReadWriteLock を使用します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Java Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「読み取り・書き込み処理のための ReadWriteLock」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このJava Academyレッスンでコードを書いて実行できますか?
はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- ReentrantLock と synchronized の比較
- 読み取り・書き込み処理のための ReadWriteLock
- Atomic 変数:ロックなしの更新
- StampedLock と楽観的読み取り