Java5には、java.util.concurrentパッケージに読み取りおよび書き込みロックが既に含まれています。それにもかかわらず、その実装の背後にある原則を理解する必要があります。
読み取り/書き込みロックのJava実装
まず、リソースへのアクセスを読み書きする条件の概要を説明しましょう。
読み取りスレッドは書き込み操作を行っておらず、書き込み操作を要求するスレッドはありません。
読み取りおよび書き込み操作を行っているスレッドはありません。
スレッドがリソースに書き込みがなく、リソースへの書き込みリクエストがない限り、スレッドがリソースを読み取ることを望んでいる場合。書き込み操作のリクエストは、読み取り操作のリクエストよりも重要であると仮定しているため、書き込み要求の優先順位を高める必要があります。さらに、読み取り操作が頻繁に発生し、書き込み操作の優先度を高めない場合、「飢er」が発生します。書き込み操作を要求するスレッドは、すべての読み取りスレッドがreadwritelockからロック解除されるまでブロックされます。新しいスレッドの読み取り操作許可が常に保証されている場合、書き込み操作を待つスレッドはブロックされ続け、結果は「飢er」になります。したがって、読み取り操作は、書き込み操作のためにスレッドがreadwritelockをロックしていない場合にのみ継続することを保証でき、スレッドは書き込み操作の準備ができてロックを要求しません。
他のスレッドが共有リソースで操作を読み取りまたは書き込みしない場合、スレッドは共有リソースの書き込みロックを取得し、共有リソースの操作を書き込むことができます。書き込みロックリクエストの公平性を確保したい場合を除き、どのスレッドが書き込みロックとどの順序で要求するかは関係ありません。
上記の説明によれば、読み取り/書き込みロックは単純に実装されており、コードは次のとおりです。
public class readwritelock {private int readers = 0; Private int Writers = 0; private int writerequests = 0; public synchronized void lockread()throws arturtedexception {while(writers> 0 || writerequests> 0){wait(); } Readers ++; } public synchronized void lockread(){readers--; notifyall(); } public synchronized void lockwrite()throws arturnedexception {writerequests ++; while(読者> 0 ||ライター> 0){wait(); } writerequests-;ライター++; } public synchronized void lockwrite()throws arturnedexception {writers--; notifyall(); }}readwriteLockクラスでは、読み取りロックと書き込みロックのそれぞれに、ロックを取得してリリースする方法があります。
読み取りロックの実装はlockread()にあります。スレッドに書き込みロック(Writers == 0)がなく、書き込みロック(Writerequests == 0)を要求するスレッドがない限り、読み取りロックを取得したいすべてのスレッドを正常に取得できます。
書き込みロックの実装はlockwrite()にあります。スレッドが書き込みロックを取得したい場合、最初に書き込みロック要求番号(Writerequests ++)に1を追加し、次に書き込みロックを実際に取得できるかどうかを判断します。スレッドが読み取りロック(Readers == 0)を保持しておらず、スレッドが書き込みロック(Writers == 0)を保持していない場合、書き込みロックを取得できます。ロックの書き込みを要求しているスレッドの数は関係ありません。
Unlockread、Unlockwriteの両方で、NotifyallメソッドがNOTIFYの代わりに呼び出されることに注意する必要があります。この理由を説明するために、次の状況を想像できます。
スレッドが読み取りロックの取得を待っている場合、スレッドが書き込みロックの取得を待っている場合。読み取りロックを待っているスレッドの1つがNotifyメソッドによって目覚めているが、Writeロック(Writerequests> 0)を要求するスレッドがまだあるため、目覚めたスレッドは再びブロッキング状態に入ります。ただし、何も起こらなかったかのように、書き込みロックを待っているスレッドは目覚めませんでした(翻訳者の注意:信号損失)。 NotifyAllメソッドを使用すると、すべてのスレッドが目覚め、要求したロックを取得できるかどうかを判断します。
NotifyAllを使用することには、1つの利点もあります。複数の読み取りスレッドが読み取りロックを待っており、lockwrite()を呼び出す後、読み取りロックを待機するすべてのスレッドがすぐに読み取りロックを取得することができます - 一度に1つだけでなく、読み取りロックを正常に取得することができます。
読み取り/書き込みロックの再突入
上記で実装された読み取り/書き込みロックはリエントラントではなく、書き込みロックをすでに保持しているスレッドが書き込みロックを再度要求するとブロックされます。その理由は、すでに書き込みスレッドがあるからです - それ自体です。また、次の例を考えてみましょう。
ReadWritElockを再発見できるようにするには、いくつかの改善を行う必要があります。以下は、読み取りロックの再突入とそれぞれ書き込みロックの再突入を処理します。
Lock Reenterを読みます
ReadWritelock Reentrantの読み取りロックを作成するには、最初にRead Lock Reentrantのルールを確立する必要があります。
スレッド内の読み取りロックが再入力されるようにするには、読み取りロックを取得する条件を満たす(書き込みまたは書き込み要求なし)、またはすでに読み取りロックを保持します(書き込みリクエストがあるかどうかに関係なく)。スレッドがすでに読み取りロックを保持しているかどうかを判断するために、マップを使用して、すでに読み取りロックを保持しているスレッドと、対応するスレッドが読み取りロックを取得する回数を保存できます。スレッドが読み取りロックを取得できるかどうかを判断する必要がある場合、マップに保存されているデータを使用して判断を下します。以下は、メソッドの変更されたコードをlockreadとlockread:
public class readwritelock {private map <thread、integer> readingthreads = new hashmap <thread、integer>(); Private int Writers = 0; private int writerequests = 0; public synchronized void lockread()throws arturnedexception {thread callingthread = thread.currentthread(); while(!cangrantreadaccess(callingthread)){wait(); } ReadingThreads.put(CallingThread、(getAccessCount(callingThread) + 1)); } public synchronized void lockread(){thread callingthread = thread.currentthread(); int AccessCount = getAccessCount(callingThread); if(accesscount == 1){readingthreads.remove(callingthread); } else {readingthreads.put(callingthread、(accesscount -1)); } notifyall(); } private boolean cangrantreadaccess(thread callingthread){if(writers> 0)return false; if(isreader(callingthread)true; if(writerequests> 0)return false; return true;} private int getReadCassCount(スレッドCallingThread){integer AccessCount = ReadingThreads.get(CallingThread); if(AccessCount == null)return 0; durtent Accesscount.intvalue();} private boolean isread(); readingthreads.get(callingthread)!= null;コードでは、スレッドに書き込みロックがない場合にのみ、読み取りロックの再突入が許可されていることがわかります。さらに、リエントラントの読み取りロックは、書き込みロックよりも優先度が高くなります。
ロック再入力を書きます
書き込みロック再入力は、スレッドがすでに書き込みロックを保持している場合にのみ許可されます(書き込みロックを取り戻します)。以下は、LockWriteとUnlockWriteのメソッドの変更されたコードです。
public class readwritelock {private map <thread、integer> readingthreads = new hashmap <thread、integer>(); private int writeaccesses = 0; private int writerequests = 0;プライベートスレッドwritingthread = null; public Synchronized void lockwrite()throws arturtedexception {writerequests ++;スレッドcallingthread = shood.currentthread(); while(!cangrantwriteaccess(callingthread)){wait(); } writerequests-; writeaccesses ++; writingthread = callingthread; } public synchronized void lockwrite()throws arturnedexception {writeaccesses--; if(writeaccesses == 0){writeThread = null; } notifyall(); } private boolean cangrantWriteAccess(thread callingthread){if(hasreaders())return false; if(writingthread == null)trueを返します。 if(!iswriter(callingthread))falseを返します。 trueを返します。 } private boolean hasreaders(){return readingthreads.size()> 0; } private boolean iswriter(thread callingthread){return writingthread == callingThread; }}現在のスレッドが書き込みロックを取得できるかどうかを判断するときに、それに対処する方法に注意してください。
ロックアップグレードを読み取り、ロックを書き込みます
時には、書き込みロックを取得するために読み取りロックを備えたスレッドが必要な場合があります。このような操作を許可するには、このスレッドは読み取りロックを備えた唯一のスレッドである必要があります。 Writelock()は、この目標を達成するためにいくつかの変更を加える必要があります。
public class readwritelock {private map <thread、integer> readingthreads = new hashmap <thread、integer>(); private int writeaccesses = 0; private int writerequests = 0;プライベートスレッドwritingthread = null; public Synchronized void lockwrite()throws arturtedexception {writerequests ++;スレッドcallingthread = shood.currentthread(); while(!cangrantwriteaccess(callingthread)){wait(); } writerequests-; writeaccesses ++; writingthread = callingthread; } public synchronized void lockwrite()throws arturnedexception {writeaccesses--; if(writeaccesses == 0){writeThread = null; } notifyall(); } private boolean cangrantWriteAccess(thread callingThread){if(isonlyreader(callingThread))return true; if(hasreaders())はfalseを返します。 if(writingthread == null)trueを返します。 if(!iswriter(callingthread))falseを返します。 trueを返します。 } private boolean hasreaders(){return readingthreads.size()> 0; } private boolean iswriter(thread callingthread){return writingthread == callingThread; } private boolean isonlyreader(スレッドスレッド){return readers == 1 && readingthreads.get(callingthread)!= null; }}これで、ReadWritelockクラスを読み取りロックから書き込みロックにアップグレードできます。
ロックダウンしてロックを読み取ります
Write Locksを持つスレッドも読み取りロックを取得したい場合があります。スレッドに書き込みロックがある場合、当然、他のスレッドには読み取りロックや書き込みロックがありません。したがって、書き込みロックを備えてから読み取りロックを取得するスレッドには危険はありません。上記のCangranTreadAccessメソッドを簡単に変更する必要があります。
public class readwritelock {private boolean cangrantreadaccess(thread callingthread){if(iswriter(callingthread))return true; if(writingthread!= null)falseを返します。 if(isreader(callingthread)true; if(writerequests> 0)return false; return true;}}}Reentrant ReadWriteLockの完全な実装
以下は、完全なreadwritelockの実装です。コードの読み取りと理解を促進するために、上記のコードが単純にリファクタリングされています。リファクタリングコードは次のとおりです。
public class readwritelock {private map <thread、integer> readingthreads = new hashmap <thread、integer>(); private int writeaccesses = 0; private int writerequests = 0;プライベートスレッドwritingthread = null; public synchronized void lockread()throws arturnedexception {thread callingthread = thread.currentthread(); while(!cangrantreadaccess(callingthread)){wait(); } ReadingThreads.put(callingThread、(getReadAccessCount(callingThread) + 1)); } private boolean cangrantreadaccess(thread callingthread){if(iswriter(callingthread))return true; if(haswriter())はfalseを返します。 if(isreader(callingthread))trueを返します。 if(haswriterequests())falseを返します。 trueを返します。 } public synchronized void lockread(){thread callingthread = thread.currentthread(); if(!isReader(callingThread)){新しいILLEGALMONITRSTATEEXCEPTION( "呼び出しスレッドは" + "このreadwriteLockに読み取りロックを保持しない"); } int AccessCount = getReadAccessCount(callingThread); if(accesscount == 1){readingthreads.remove(callingthread); } else {readingthreads.put(callingthread、(accesscount -1)); } notifyall(); } public synchronized void lockwrite()throws arturnedexception {writerequests ++;スレッドcallingthread = shood.currentthread(); while(!cangrantwriteaccess(callingthread)){wait(); } writerequests-; writeaccesses ++; writingthread = callingthread; } public synchronized void lockwrite()throws arturnedexception {if(!iswriter(thread.currentthread()){新しいIllegalMonitorStateException( "Calling thread not" + "このreadwriteLockに書き込みロックを保持します");} writeacasses--(writeackesses = = 0) Boolean CangrateaCcess(ISONLYREADER(CallingThread))if(whientthread == null) if(accesscount.intvalue(){return readingthreads.size()> 0; readingthreads.get(callingthread)!= null; } private boolean haswriter(){return writeThread!= null; } private boolean iswriter(thread callingthread){return writingthread == callingThread; } private boolean haswriterequests(){return this.writerequests> 0; }}最後にunlock()を呼び出します
readwritelockを使用してクリティカルゾーンを保護する場合、クリティカルゾーンが例外をスローする場合は、最終的なブロックでreadunlock()とwriteunlock()を呼び出すことが重要です。これは、readwriteLockが正常にロック解除され、他のスレッドがロックを要求できるようにするために行われます。これが例です:
lock.lockwrite(); try {//クリティカルセクションコードを実行します。上記のコード構造は、臨界領域に例外がスローされたときに、readwritelockもリリースされることを保証できます。 UnlockWriteメソッドが最終的なブロックで呼び出されない場合、批判的セクションに例外がスローされた場合、ReadWritElockはWrite Lock状態にとどまり、すべてのスレッドがLockRead()またはLockWrite()を呼び出すすべてのスレッドをブロックします。 readwritelockを再実行できる唯一の要因は、readwriteLockがリエントラントであることです。例外がスローされると、スレッドはロックを正常に取得し、クリティカルセクションを実行してlockwrite()を再度呼び出します。しかし、スレッドがロックを取得しなくなった場合はどうなりますか?したがって、最終的にUnlockwriteを呼び出すことは、堅牢なコードを作成するために非常に重要です。
上記は、Javaマルチスレッド情報の編集です。今後も関連情報を追加し続けます。このウェブサイトへのご支援ありがとうございます!