読書ノート------効率的かつ軽量なロック

3739 ワード

Java仮想マシンスタック上のオブジェクトには、エンティティデータの格納に加えて、オブジェクトヘッダの概念があることを知っています.hashCode、GC世代別年齢、クラスへのポインタなどの情報が格納されています.オブジェクトヘッダのデータの大部分は必須ではありませんが、仮想マシンがいくつかの機能をより便利かつ迅速に完了するのに役立ちます.たとえば「クラスを指すポインタ」は、反射を容易に実現できます(このポインタがなくても、仮想マシンは反射を完了できますが、面倒になる可能性があります).
「重要でない」だけに、オブジェクトヘッダのデータ構造は非常に柔軟で、オブジェクトが異なる状態にある場合、必要に応じて異なるものを格納します.例えば、オブジェクトがロックされていない場合、ハシコード、GC世代別年齢などを記憶することができ、その場合は自分の状態フラグを1にする.オブジェクトが軽量レベルでロックされている場合、軽量レベルのロックを指すポインタが格納され、その場合は自分の状態フラグを0にする.オブジェクトがヘビー級ロックされている場合,オペレーティングシステムの反発量を指すポインタを格納し,自分の状態フラグを2に設定するなどした.
軽量級の鍵は何ですか?共有データのセキュリティを保証するためにsynchronized同期ブロックのような手段を用いて共有データのセキュリティを保証しなければならないことを知っています.しかし、同期は通常、スレッドの停止と起動に関連するスレッドのブロックを意味し、Javaのスレッドはオペレーティングシステムの物理スレッドにマッピングされ、これらの停止と起動操作は、ユーザ状態とカーネル状態の間でプログラムを絶えず切り替える必要があり、これは小さなオーバーヘッドではない.同時実行が正確であることを保証するために、共有データにロックをかける操作は避けられないが、実際には、いくつかのスレッドが本当に同じ時間に同じ同期ブロックに入ると約束する機会はどれくらいあるのだろうか.少ないようです.悲しいことに、1つのスレッドだけでも、仮想マシンはロック、ロック解除、ロックカウンタのメンテナンスを実行しなければなりません.ユーザ状態-カーネル状態切り替えなどの操作(まあ、スレッドが1本しかない場合、ブロックの問題はありませんが、ロックとロック解除は必須ですし、何の最適化もしなければ、ロックとロック解除はオペレーティングシステムの反発量を使うので、カーネル状態に切り替えなければなりません).だからこの場合に最適化の余地はありますか?軽量レベルロックはこの問題を解決するために発明されました.
軽量レベルのロックの原理と実行過程は、ここでは本を写さない.参考になる
仮想マシンでのロック最適化の概要(適応性スピン/ロック粗化/ロック削除/軽量ロック/バイアスロック)この文章の作者は周志明大本人です.
この中にはこんな言葉があります.
この更新操作が失敗した場合、仮想マシンはまず、オブジェクトのMark Wordが現在のスレッドのスタックフレームを指しているかどうかをチェックします.現在のスレッドがこのオブジェクトのロックを持っていることを示す場合は、同期ブロックに直接入って実行を続行することができます.そうしないと、このロックオブジェクトが他のスレッドによってプリエンプトされていることを示します.最初はCAS操作が失敗した以上、Mark Wordがロックレコードを指すポインタがあるはずがないのではないかと疑問に思っていました.実はこの言葉は2回目、3回目、N回目のロック過程に対して、1回目のロック過程は確かにこのような状況はありません.もう一つ、CAS操作の比較は何ですか?ロックをかけるとき、比較するのは対象のMark Wordとスタック上のMark Wordです.ロックを解除するときは、ロックしたばかりのMark Wordをメモしておいて、現在のMark Wordと比較しておくと思います.
最後に本をもう少し写して、偏向ロックについて.
偏向ロックもJDK 1.6に導入されたロック最適化であり、競合のないデータの同期原語を排除し、プログラムの実行性能をさらに向上させることを目的としている.軽量レベルのロックが競合なしでCAS操作を用いて同期使用の反発量を除去する場合,バイアスロックは競合なしで同期全体を除去し,CAS操作さえ行わない.
偏向ロックの「偏」は、偏心の「偏」、肩を持つ「偏」である.このロックは、最初に取得したスレッドに偏り、次の実行中に他のスレッドに取得されなかった場合、偏りロックを持つスレッドは同期を必要としません.
バイアスロックは、そのロックを取得したスレッドのIDをオブジェクトヘッダに記録する.
補足:CASプロセスの修正について
CASの意味は、CASには3つのオペランド、メモリ値V、古い予想値A、変更する新しい値Bがあります.そして、メモリ値Vは、予想値Aとメモリ値Vとが同時にBに変更された場合にのみ、何もしない.だから、私の推測は:
ロックプロセス:CASの前に、ロックレコードのownerポインタがオブジェクトのMark Wordを指しています.CAS操作は、対象の現在のMark WordとスタックにコピーしたばかりのMark Wordが等しいか否かを判断し、等しい場合は対象のMark Wordをロックレコードへのポインタとする.このCAS操作が成功すると、本スレッドはロックに成功し、次に、Objectロックフラグビットを「00」(軽量ロック)と表記する.そうでなければ、対象Mark Wordのポインタが現在のスレッドのロックレコードを指しているかどうかをチェックする.現在のスレッドがロックされていることを説明すれば、今回のロックは再入プロセスであり、同期ブロックに入って直接実行することができる.そうでなければ、ロックが他のスレッドに奪われたことを説明する.(Mark Wordのコピーとownerポインタの設定中です.私が先に始めるかもしれませんが、他の人はもっと速いです)すると、オブジェクトのロックフラグをヘビーロックに変更し、Mark Wordのロックポインタをオペレーティングシステムへの反発量に変更し、オブジェクトがヘビーロックに膨張し、このスレッドはブロック状態に入ります.
ロック解除プロセス:ロックと同様です.少し違いますが、今回比較したのはスタック上のMark WordのアドレスとオブジェクトMark Wordポインタが指すアドレスが同じかどうかです.異なる場合は1つの解釈しかありませんが、他のスレッドがこの間にロックを申請し、対象が重量ロックに膨張した場合、ブロックされたスレッドを呼び覚ます必要があります.

if (header.flag == 00) {
    assign space on stack frame: Lock_Record;
	Lock_Record.header <== object.header;
	Lock_Record.pointer --> object.header;
	
	success <== CAS(object.header, Lock_Record.header, object.header.pointer --> Lock_Record and object.header.flag <== 01);
	
	if (success) {
	    OK;
	} else {
	    reenter <== object.header.pointer --> Lock_Record ??
		if (reenter) {
		    OK;
		} else {
		    object.header.flag <== 02;
			blocked;
		}
	}
} else if (header.flag == 01) {
    object.header.flag <== 02;
    blocked;
} else if (header.flag == 02) {
    blocked;
}