内蔵錠と明示錠の違い--JCIP C 13読書ノート


[本文は私のJava Concurrency In Practice C 13に対するまとめと総括である. 転載は作者と出典を明記してください. 间违いがあれば、コメントで指摘してください.
どのjavaオブジェクトも同期のロックとして使用することができ、区別を容易にするために内蔵ロックと呼ぶ.
JDK 5.0は明示的なロックを導入した:Lockとそのサブクラス(例えばReentrantLock、ReadWriteLockなど). 
内蔵ロックと明示的ロックの違いは次のとおりです.
 
1.申請を中断することができる
synchronizedを使用して内蔵ロックを申請する場合、ロックは他のスレッドによって保持され、現在のスレッドは保留され、ロックが再利用され、待機中は中断できません.明示的なロックは中断可能な申請を提供します.  
public class InterruptedLock extends Thread {
	private static Lock lock = new ReentrantLock();

	@Override
	public void run() {
		try {
			//      ,                  ,    InterruptedException  
			lock.lockInterruptibly();
		} catch (InterruptedException e) {
			System.out.println("interruption happened");
			return;
		}

		//        ,         ,        
		try {
			System.out.println("run is holding the lock");
		} finally {
			lock.unlock();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		try {
			lock.lock();
			System.out.println("main is holding the lock.");
			Thread thread = new InterruptedLock();
			thread.start();
			// 1s   thread  ,           lockInterruptibly   
			Thread.sleep(1000);
			//   thread        InterruptedException  .
			thread.interrupt();
			Thread.sleep(1000);
		} finally {
			lock.unlock();
		}
	}
} 

 
2.試行申請
Lock.tryLockおよびLock.tryLock(long time,TimeUnit unit)メソッドは、ロックの取得を試みるために使用されます.試行が成功しなかった場合はfalseに戻り、そうでなければtrueに戻ります.組み込みロックはこのような特性を提供しません.組み込みロックの申請を開始すると、申請が成功するまでスレッドが中断できず、申請もキャンセルできません.Lockの試行型申請は、通常、時間限定のtaskを実現するために使用されます.
public boolean transferMoney(Account fromAcct, Account toAcct, DollarAmount amount, long timeout, TimeUnit unit)
		throws InsufficientFundsException, InterruptedException {
	long fixedDelay = getFixedDelayComponentNanos(timeout, unit);
	long randMod = getRandomDelayModulusNanos(timeout, unit);
	//     
	long stopTime = System.nanoTime() + unit.toNanos(timeout);

	while (true) {
		if (fromAcct.lock.tryLock()) {
			try {
				if (toAcct.lock.tryLock()) {
					try {
						if (fromAcct.getBalance().compareTo(amount) < 0)
							throw new InsufficientFundsException();
						else {
							fromAcct.debit(amount);
							toAcct.credit(amount);
							return true;
						}
					} finally {
						//              
						toAcct.lock.unlock();
					}
				}
			} finally {
				//              
				fromAcct.lock.unlock();
			}
		}
		//               false,         .         .
		if (System.nanoTime() < stopTime)
			return false;
		NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);
	}
}

試行型出願も中断可能である.
 
3.ロックの解除
内蔵ロックについては、コードが同期コードブロックの外に実行されると自動的にロックが解除され、開発者は異常を投げ出す心配がなく、メソッドの戻りなどが発生した場合にロックが解放される問題はない.しかし、明示的なロックについてはunlockメソッドを呼び出してロックを解放しなければならない.この場合、開発者自身が放出異常を処理する必要がある.メソッドが戻る場合など.通常finallyコードブロックでロックの解放が行われるが,ロックを申請する後にのみロックを解放する必要があり,未所持のロックを解放すると未検査異常が放出される可能性があることに注意する.
従って、内蔵ロックの使用はより容易であるが、明示的ロックの使用は煩雑であるが、明示的ロックの解放方式の煩雑さは、ロックの申請と解放が同じコードブロックにある必要がないという便利な点をもたらす.
 
4.公平ロック
ReentrantLock(boolean fair)コンストラクション関数によってReentrantLockロックを作成する場合、デフォルトでは不公平ロックとして公平ポリシーを指定できます.
複数のスレッドがフェアロックを申請する場合、申請時間の早いスレッドは優先的にロックを取得するが、フェアロックでは割り込みが許可され、あるスレッドがロックを申請する際にロックが適切に利用可能である場合、そのスレッドはキューに並ばずに直接ロックを取得する.例えば、スレッドBが不公平ロックを申請する場合、そのロックはスレッドAによって保持されており、スレッドBはロックを解除すると、スレッドBは、保留中の状態から回復し、再度申請しようとする(このプロセスには一定時間がかかる).このときにスレッドCがロックを申請する場合、不公平なポリシーにより、スレッドCは直ちにロックを取得して実行を開始することができる.スレッドCが短い時間後にロックを解除したと仮定すると、スレッドBは回復プロセスを完了していない可能性がある.スレッドCのサスペンションからリカバリまでの時間を節約する、スレッドBの実行を遅らせていないため、ロック競合が激しい場合、不公平なポリシーはプログラムのスループットを向上させることができる.
内蔵ロックは不公平なポリシーを採用するが、明示的なロックは不公平なポリシーを使用するかどうかを指定することができる.
 
5.起動と待機
スレッドは、waitを内蔵ロックに、または内蔵ロックのnotifyまたはnotifyAllメソッドを呼び出すことによって、待機するスレッドを起動することができるが、内蔵ロックに複数のスレッドがある場合、特定のスレッドを正確に起動することはできない.
明示的なロックは起動と待機にも使用できます.Lock.newConditionメソッドを呼び出すとConditionオブジェクトが得られます.Condition.awaitメソッドを呼び出すとスレッドが待機し、Condition.singalまたはCondition.singalAllメソッドを呼び出すと、そのConditionオブジェクト上で待機しているスレッドが起動します.同じ明示的なロックから複数のConditionオブジェクトが派生します.そのため、私たちは正確な目覚ましを実現することができます.具体的な応用は私の初期のブログを参照してください.http://coolxing.iteye.com/blog/1236696
 
ロックの最適化
JDK 5.0に明示的なロックを加えた後、開発者は明示的なロックが内蔵ロックに比べて明らかな性能の優位性を持っていることを発見し、更に明示的なロックの多くの新しい特性を加えて、多くの文章と書籍は明示的なロックを内蔵ロックの代わりに使用することを推薦した.しかしJDK 6.0は内蔵ロックに対して大量の最適化を行い、明示的なロックはすでに明らかな性能の優位性を備えていない.だからJDK 6.0とその後のバージョンを使用するならば、明示的なロックによって提供される新しい機能が使用されていない場合、明示的なロックを意図的に使用する必要はありません.理由は次のとおりです.
1.内蔵ロックはJVMの内蔵特性であり、より容易に最適化することができる.
2.thread dumpのようなモニタリングプログラムは内蔵ロックに対してより良いサポートを持つ. 
3.ほとんどの開発者は内蔵ロックに詳しい.
JDK 6.0の内蔵ロックに対する最適化措置は「java仮想マシンを深く理解する」13.3節を参照することができる.