2016-12-13 24 views
0

ReentrantReadWriteLockを使用して、多数の異なる読み書き操作を持つかなり複雑なデータ構造へのアクセスを制御しようとしたときにポップアップした質問があります。ドキュメントの例では、読み書きロックを取得しましたが、私はこのデータ構造への同時アクセスを管理するために(私が知る限り)使用しています。しかし、別の問題をデバッグする際に、同じスレッドで複数の読み取りロックを取得することがあることに気付きました。これの基本的な理由は、より単純なクエリを呼び出す複雑なクエリが多数あることです(下記の例を参照)。複雑なクエリは、トランザクションとみなすことができます。つまり、getVersion()dataへのアクセスは、myQueryには書き込まれません。この現在の解決策は動作しますが、コード内のいくつかの箇所では、同じスレッドが持つ複数の読み取りロックを持つことになります。私は書き込み同等物がisHeldByCurrentThread()メソッドを持っていることに気づいたが、これはreadLockに奇妙に存在しない。私は以下を見つけることができませんでした:`ReentrantReadWriteLock`の読み込みロックの確認

  • 同じスレッドで複数の読み取りロックを持つのは悪いですか(不良なスタイル、パフォーマンス、または将来のバグの危険性)?
  • WriteLock.isHeldByCurrentThreadを使用してエラーをチェックするのは悪いです(たとえば、書き込みロックが必要ですが、存在しないか、書き込みロックを解除する条件)。
  • これが問題になった場合の進め方を決めるベストプラクティスはありますか? lockedAquiredブール値を、すでにロックを持っているコードから呼び出すことができるメソッドに渡すのですか?それらが一意に取得されることを保証するのですか、またはReadLock.tryLock()を使うべきですか?

は、ここでのコードサンプルです:

public class GraphWorldModel { 

    //unfair RW Lock (default, see javadoc) 
    protected final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(false); 
    protected final ReentrantReadWriteLock.ReadLock readLock = rwl.readLock(); 
    protected final ReentrantReadWriteLock.WriteLock writeLock = rwl.writeLock(); 

    protected long versionID = 0; 
    protected Object data = null; 

    @Override 
    public long getVersion() { 
     long result = -1; 
     readLock.lock(); 
     try { 
      result = this.versionID; 
     } finally { 

      readLock.unlock(); 
     } 
     return result; 
    } 
    public ResultObject myQuery() { 
     //do some work 
     readLock.lock(); 
     try { 
     long version = getVersion(); 
     ResultObject result = new ResultObject(this.data, version); 
     //note: querying version and result should be atomic! 
     } finally { 
     readLock.unlock(); 
     } 
     //do some more work 
     return result; 
    } 
} 
+1

ロックは、その名前が示すとおり、リエントラントです。あなたがすでにそれを所有しているときに再びそれを取得することは、まったく問題ではありません。あなたが持っているものは完璧です。 –

答えて

1
  • はそれが悪い(貧弱なスタイル、パフォーマンスや将来のバグのリスク)で同じスレッドで複数の読み取りロックを持っていますか?

私はそれが疑わしいスタイルだと言います。まず、@ JBNizetが指摘するように、既に所有しているロックを取得しようとしても問題はありません。ロックはリーダのカウントを1だけインクリメントし、最後にロックを解除するとそれをデクリメントします。

ただし、パフォーマンスの低下を意味する共有ロック統計を更新するには、メモリバリア(volatileの読み取り値)を超えなければならないことを意味します。これは、パフォーマンスの違いが顕著に現れるかどうかに関して、このコードがどのくらい頻繁に実行されるかによって異なります。

しかし、バグの面では大きな問題があります。読み込み専用のロックについて話しているのであれば、バグの可能性はもっと高いとは思えません。実際にダブルロックの問題を修正しようとすると(下記参照)、おそらくリスクが高くなります。しかし、あなたのコードでは、try/finallyロジックは、ロックが適切に使用され、完了するとロックが解除されることを保証します。

ただし、あなたがロックを読み書き混合の話をしているならば、あなたは、次のコードのデッドロックを理解する必要があります。

ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); 
ReadLock readLock = rrwl.readLock(); 
WriteLock writeLock = rrwl.writeLock(); 
readLock.lock(); 
writeLock.lock(); 

あるいは、少なくともいくつかの他のコードがread-lockのロックを解除するまで、それが停止します。コードに読み書きロックを混在させる場合は、二重ロックから保護する必要があります。

  • (例えば、書き込みロックを期待どれもが存在しない、または書き込みロックを解除する条件として)エラーをチェックするWriteLock.isHeldByCurrentThreadを使用することが悪いですか?

これはパフォーマンスヒットが大きい場合には、よりリスクの高い、それ多分価値がある、あなたのコードにロジックの多くを追加する予定です。つまり、以下の代替案を参照してください。

  • これが問題である場合の処理​​方法を決定することをお勧めありますか?既にロックを持っているコードから呼び出されるメソッドにlockedAquiredブール値を渡しますか?一意に取得されているかどうか、またはReadLock.tryLock()を使用する必要がありますか?私はどうなるのか

getVersionLocked()と呼ばれるカーネル法を作成することです。ような何か:

@Override 
public long getVersion() { 
    readLock.lock(); 
    try { 
     // NOTE: I refactored this to return out of the try/finally which is a fine pattern 
     return getVersionLocked(); 
    } finally { 
     readLock.unlock(); 
    } 
} 
public ResultObject myQuery() { 
    //do some work 
    readLock.lock(); 
    ResultObject result; 
    try { 
     long version = getVersionLocked(); 
     result = new ResultObject(this.data, version); 
     //note: querying version and result should be atomic! 
    } finally { 
     readLock.unlock(); 
    } 
    //do some more work 
    return result; 
} 
// NOTE: to call this method, we must already be locked 
private long getVersionLocked() { 
    return this.versionID; 
} 

次に、あなたがあなたの方法のロックまたはアンロックバージョンを呼び出し、一度だけreadLock()を行うための意思決定を行うことができます。

ロックがではなくが保持されているときにgetVersionLocked()と呼ぶかもしれないという点でこれはもっと危険ですが、それは容認できるリスクだと思います。

+0

'メモリ同期'はありません。スレッドホールドカウントはスレッドごとに保持されます。 'java.util.concurrent'は主に完全に同期化されていなくても大部分です。 – EJP

+0

あなたは正しい@EJPとは思わない。 'tryAcquireShared(...) 'でヒットした' Sync'クラスに 'volatile int state'があります。読んだら、' ReadLock.lock() 'へのすべての呼び出しに' volatile'アクセスがあります。私はホールドカウントがスレッドローカルであることに同意しますが。 – Gray

関連する問題