Javaでsynchronizedを再帰的に呼び出した時の挙動

一般的にはやらないだろうけど、気になって調べてみた。
 

ソース

OpenJDK、Hotspot VMをベースに調べてみた。
 

JDK

https://github.com/openjdk-mirror/jdk7u-jdk
commit: f4d80957e89a19a29bb9f9807d2a28351ed7f7df
 

Hostspot VM

https://github.com/openjdk-mirror/jdk7u-hotspot
commit: 50bdefc3afe944ca74c3093e7448d6b889cd20d1
 

Javaソースコードをパースする

何はともあれ、まずはパース。
 
パーサはここらへんに定義されてる。
パーサ自身がJavaで書かれてる。
jdk7u-jdk/src/share/classes/sun/tools/java/Parser.java
Parser::parseStatement()

1260           case SYNCHRONIZED: {
1261             // synchronized-statement: synchronized (expr) stat
1262             long p = scan();
1263             expect(LPAREN);
1264             Expression e = parseExpression();
1265             expect(RPAREN);
1266             return new SynchronizedStatement(p, e, parseBlockStatement());
1267           }

 
どうやらsynchronized以下のブロック(式)はSynchronizedStatementってクラスで表現されている。
jdk7u-jdk/src/share/classes/sun/tools/tree/SynchronizedStatement.java
 

パースされた構文木JVMバイトコードに変換する

Javaソースコードは、パースされてJVMバイトコードに変換される。
 
SynchronizedStatement::code(Environment env, Context ctx, Assembler asm)
バイトコードに変換されるらしい。

136         // lock the object
137         asm.add(where, opc_astore, num1);
138         asm.add(where, opc_aload, num1);
139         asm.add(where, opc_monitorenter);
140
141         // Main body
142         CodeContext bodyctx = new CodeContext(ctx, this);
143         asm.add(where, opc_try, td);
144         if (body != null) {
145             body.code(env, bodyctx, asm);
146         } else {
147             asm.add(where, opc_nop);
148         }
149         asm.add(bodyctx.breakLabel);
150         asm.add(td.getEndLabel());
151
152         // Cleanup afer body
153         asm.add(where, opc_aload, num1);
154         asm.add(where, opc_monitorexit);
155         asm.add(where, opc_goto, endLabel);

 
JVMアセンブリでは、オペコードmonitorenterでオブジェクトをロック、オペコードmonitorexitでオブジェクトをアンロック。
Javaのsynchronizedステートメントは、ロック→ステートメント実行→アンロック、と展開されてバイトコードに変換される。
 
ここで生成されたバイトコードJVMによって実行されるらしい。
その辺は、環境によって実行方法が異なるみたい。
Linuxであればunixsocketを使ってJVMに接続しているようだけど、今回知りたい事とは逸れるからあんまり調べてない。
 

monitorenter/monitorexitの実装

ここからはJVM側のソースを見る。
fast-pathとかslow-pathとか、synchronized修飾子かsynchronizedステートメントかとか、実装がいろいろあって読むのしんどい。
 
jdk7u-hotspot/src/cpu/x86/vm/assembler_x86.cpp
int MacroAssembler::biased_locking_enter(Register lock_reg, Register obj_reg, Register swap_reg, Register tmp_reg, bool swap_reg_contains_mark, Label& done, Label* slow_case, BiasedLockingCounters* counters)
void MacroAssembler::biased_locking_exit(Register obj_reg, Register temp_reg, Label& done)
 
jdk7u-hotspot/src/share/vm/runtime/synchronizer.cpp
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS)
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS)
 
jdk7u-hotspot/src/share/vm/runtime/objectMonitor.cpp
void ATTR ObjectMonitor::enter(TRAPS)
void ATTR ObjectMonitor::exit(TRAPS)
 

ObjectMonitor

ObjectMonitorってのは、
オブジェクトのロックをしているThreadを表す _owner と、
monitorenterを何度ネストしているかっていうカウンター _recursions を持つ。(めっちゃ要約)
 
Javaから見るとsynchronizedをネストすると _recursions の値がインクリメントされ、
synchronizedのステートメントを抜けると _recursions の値がデクリメントされる。
_recursions が0になったらアンロックされる。
 

まとめ

synchronizedステートメントやsynchronized修飾子のついたメソッドを再帰的に呼び出しても自分がしたロックで自分自身がデッドロックを起こすような事はない、と。
 
JVM側のくだりになったあたりで力尽きてしまった。
ちゃんと知りたければソース読む方がいい…。
この辺の話題ってJVMを知らないとググろうにもキーワードが解らなかったりしたから、とりあえずJVMへの入り口として一応記事にした。
JVMをちょっとでも知ってからJavaとかScalaとかの言語仕様に迫ると理解が深まるかもネ…?
 

JavaのsynchronizedやJVMのロックに関する参考

Jvm reading-synchronization
https://www.slideshare.net/nminoru_jp/jvm-readingsynchronization
 
モニタ同期・待機処理と再帰ロックサポート - yohhoyの日記
http://d.hatena.ne.jp/yohhoy/20161213/p1
 
synchronizedは実装詳細である - yohhoyの日記
http://d.hatena.ne.jp/yohhoy/20130401/p1
 
同期排他処理 : ロック確保処理 (monitorenter バイトコード, synchronized メソッド)
http://hsmemo.github.io/articles/noOroadKvi.html
 
同期排他処理 : ロック確保処理 : fast-path の処理 : synchronized method のエントリ部の処理 : Template Interpreter での処理
http://hsmemo.github.io/articles/no9662EYy.html
 
同期排他処理 : ロック解放処理 : fast-path の処理 : synchronized method の exit 部 : JVMTI の PopFrame()/ForceEarlyReturn() 時 : Template Interpreter での処理
http://hsmemo.github.io/articles/no3059hIn.html
 
ObjectMonitor クラス関連のクラス (ObjectWaiter, ObjectMonitor)
http://hsmemo.github.io/articles/noBIzmHjmm.html
 
ObjectSynchronizer クラスおよび ObjectLocker クラス (ObjectSynchronizer, ObjectLocker, 及びそれらの補助クラス(ReleaseJavaMonitorsClosure))
http://hsmemo.github.io/articles/noL3z0U0-A.html
 
BasicLock クラスおよび BasicObjectLock クラス (BasicLock, BasicObjectLock)
http://hsmemo.github.io/articles/noBMKPZJux.html