マルチスレッドの安全性はまったく問題ありません!読み取りおよび書き込みロックはStringBuilderへのアクセスを保護し、コードはクリーンで読みやすいです。
ReentrantReadWriteLockを使用することで、複数のリーダーが一緒に進むことができるため、実際には、より高度な同時実行性を達成する可能性を最大化できます。これは、従来の同期メソッドを使用するよりも優れたソリューションです。ただし、質問で述べられていることとは反対に、コードは、リーダーが読んでいる間、ライターが書くことを許可しません。ただし、これ自体は必ずしも問題ではありません。
リーダーは、先に進む前に読み取りロックを取得します。ライターは、続行する前に書き込みロックを取得します。読み取りロックのルールでは、書き込みロックがない場合に取得できます(ただし、読み取りロックがいくつかある場合、つまり、よりアクティブなリーダーがある場合は問題ありません)。書き込みロックのルールでは、他にロックがない場合(リーダー、ライターがない場合)にのみ取得できます。したがって、複数のリーダーが許可されますが、ライターは1つだけです。
必要になる可能性がある唯一の変更は、ロック初期化コードを次のように変更することです。
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
質問で与えられた元のコードがそうであるように、ロックが公平である必要はありません。上記の変更により、「スレッドはほぼ到着順序のポリシーを使用してエントリを争うことが保証されます。書き込みロックが解放されると、最も長く待機しているシングルライターに書き込みロックが割り当てられるか、リーダーがより長く待機している場合に割り当てられます。どのライターでも、リーダーのセットには読み取りロックが割り当てられます。不公平として構築された場合、ロックへのエントリの順序は到着順序である必要はありません。」(http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.htmlから取得)
以下も参照してください(同じソースから):
ReentrantReadWriteLocksは、特定の種類のコレクションの一部の使用における同時実行性を向上させるために使用できます。これは通常、コレクションが大きく、ライタースレッドよりも多くのリーダースレッドによってアクセスされ、同期オーバーヘッドを上回るオーバーヘッドを伴う操作を伴う場合にのみ価値があります。たとえば、これは、大きくて同時にアクセスされることが予想されるTreeMapを使用するクラスです。
class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock(); try { return m.get(key); } finally { r.unlock(); }
}
public String[] allKeys() {
r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); }
}
public Data put(String key, Data value) {
w.lock(); try { return m.put(key, value); } finally { w.unlock(); }
}
public void clear() {
w.lock(); try { m.clear(); } finally { w.unlock(); }
}
}
APIドキュメントからの抜粋は、特にパフォーマンスを重視しています。特定のケースでは、「大規模なコレクション」の基準を満たしているかどうかについてコメントすることはできませんが、コンソールへの出力は、スレッドセーフメカニズムのオーバーヘッドよりもはるかに時間がかかると言えます。いずれにせよ、ReentrantReadWriteLocksを使用することは、論理的な観点からは完全に理にかなっており、完全にスレッドセーフです。これは読むのに良いコードです:-)
注1(元の質問のコメントにある例外に関する質問への回答):http ://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Lock.htmlから取得
lock()はロックを取得します。ロックが使用できない場合、現在のスレッドはスレッドスケジューリングの目的で無効になり、ロックが取得されるまで休止状態になります。
ロックの実装は、デッドロックを引き起こす呼び出しなど、ロックの誤った使用を検出できる場合があり、そのような状況では(チェックされていない)例外をスローする場合があります。状況と例外タイプは、そのLock実装によって文書化する必要があります。
ReentrantReadWriteLock.ReadLockの関連ドキュメント(http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.ReadLock.html)には、このような例外の表示はありません。またはReentrantReadWriteLock.WriteLock(http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.WriteLock.html)
注2:StringBuilderへのアクセスはロックによって保護されていますが、System.outは保護されていません。特に、複数のリーダーが同時に値を読み取り、同時に出力しようとする場合があります。System.out.println()へのアクセスが同期されているため、これも問題ありません。
注3:複数のアクティブなライターを禁止したいが、ライターと1つ以上のリーダーを同時にアクティブにしたい場合は、読み取りロックを完全に使用してスキップする、つまりdelete lock.readLock()。lock();を実行できます。およびlock.readLock()。unlock(); あなたのコードで。ただし、この特定のケースでは、これは間違っています。StringBuilderへの同時読み取りと書き込みを停止する必要があります。