◆Javaでの ReadWriteLock サンプルプログラム(改善版) 【notifyAll() の実行はCPUパワーをかなり消費し、実行速度が低下します】
ReadWriteLockを利用して少しでもRead処理の高速化を図るつもりが、私の利用環境&CPUパワーの少ないマシンでは、シンプルにsynchronizedでアクセス毎に該当オブジェクトをロックしてアクセスする方が明らかに高速(High performance)だったりしました。
readLock()&readUnlock() しか実行していないのに遅すぎるため原因を探ったところ、参考にしたプログラムでは無駄にnotifyAll() が実行されることによって速度が低下していました。そこで、notifyAll() が不要な場合は実行しないように改善してみたのが下記です。これによって無用な処理速度の低下が防止できます。キモはnotifyAll()の前に判定処理を入れたところです。
#下記は、writeLock() が優先される作り(writeLock呼び出しが続くとreadLockが取得できない)となっています。
final class ReadWriteLocks {
private int lockCounter = 0;
private int waitingReadLockCounter = 0;
private int waitingWriteLockCounter = 0;
private boolean isWriting = false;
final synchronized int getNumber() {
return lockCounter;
}
final synchronized void readLock() {
waitingReadLockCounter++;
while (isWriting || waitingWriteLockCounter > 0) {
try {
System.out.println("readLock wait =================");
wait();
} catch (InterruptedException e) { }
}
waitingReadLockCounter--;
lockCounter++;
}
final synchronized void readUnlock() {
lockCounter--;
if (lockCounter == 0) {
if (waitingWriteLockCounter != 0) {
notifyAll(); // wait中がいる場合のみ実行
}
}
}
final synchronized void writeLock() {
waitingWriteLockCounter++;
while (isWriting || lockCounter > 0) {
try {
System.out.println("writeLock wait =================");
wait();
} catch (InterruptedException e) { }
}
waitingWriteLockCounter--;
isWriting = true;
}
final synchronized void writeUnlock() {
isWriting = false;
if ((waitingReadLockCounter != 0) || (waitingWriteLockCounter != 0)) {
notifyAll(); // wait中がいる場合のみ実行
}
}
}
◆Javaでの ReadWriteLock サンプルプログラム 【lock取得できなかったら寝込むバージョン】
notifyAll() は、呼び出されるとwait()中のスレッドが全て起こされます(順序は不定)。
せっかく起こされて動作開始してもwhile内の条件に合致していれば再度wait()し、次の(処理の重い)notifyAll() を待つことになり、まだ少々無駄があります。こちらのプログラムでは、lockが取れなければしばらく sleep(寝て)、起きたら再度lock取得を試すというとってもシンプルなものになります。readLockされている時間が短く(readLockされていない時間が長く)、write処理開始にリアルタイム性を求めない場合にはこちらが良いかと思います。
#下記は、readLock() が優先される作り(readLock呼び出しが続くとwriteLockが取得できない)となっています。
[FirstReadWriteLock.java]
-------
final class FirstReadWriteLock {
private int lockNumber = 0;
final synchronized int getNumber() {
return lockNumber;
}
final synchronized boolean readLock() {
if (lockNumber == -1) {
return false;
} else {
lockNumber++;
return true;
}
}
final synchronized void readUnlock() {
lockNumber--;
}
final synchronized boolean writeLock() {
if (lockNumber != 0) {
return false;
} else {
lockNumber = -1;
return true;
}
}
final synchronized void writeUnlock() {
lockNumber = 0;
}
}
-------
[HogeServer.java]
---
static FirstReadWriteLock rwLock = new FirstReadWriteLock();
---
[readLock使用]
---
try {
while( HogeServer.rwLock.readLock() == false ) {
System.out.println("read sleep........");
sleep(50); // 寝込む
}
} catch (InterruptedException ie) {
System.out.println("Interrupt...");
}
// 【参照処理(read処理)を記述】
HogeServer.rwLock.readUnlock();
---
[writeLock使用]
---
try {
while( HogeServer.rwLock.writeLock() == false ) {
System.out.println("write sleep........");
Thread.sleep(50); // 寝込む
}
} catch (InterruptedException ie) {
System.out.println("Interrupt...");
}
// 【更新処理(write処理)を記述】
HogeServer.rwLock.writeUnlock();
---
- 初期段階で、Java標準のReentrantReadWriteLock も試しましたが、私の検証環境ではread限定の計測で遅すぎてお話にならなかったです。# 毎度 synchronizedしてread の方が圧倒的に速かった。
・・・OpenJDK13 にて