私はjdk81212-b04
、Eclipse 4.13で実行されているUbuntu LinuxでAdoptOpenJDK を使用しています。Swingにはラムダ内にラムダを作成するメソッドがあります。おそらく両方とも別々のスレッドで呼び出されます。これは次のようになります(疑似コード):
private SwingAction createAction(final Data payload) {
System.out.println(System.identityHashCode(payload));
return new SwingAction(() -> {
System.out.println(System.identityHashCode(payload));
//do stuff
//show an "are you sure?" modal dialog and get a response
//show a file selection dialog
//when the dialog completes, create a worker and show a status:
showStatusDialogWithWorker(() -> new SwingWorker() {
protected String doInBackground() {
save(payload);
}
});
ラムダは数層の深さであり、最終的にはキャプチャされた「ペイロード」がファイルに保存されることがわかります。
しかし、レイヤーとスレッドを検討する前に、問題に直接移動しましょう。
- 初めて呼び出した
createAction()
とき、2つのSystem.out.println()
メソッドはまったく同じハッシュコードを出力しcreateAction()
ます。これは、ラムダ内でキャプチャされたペイロードがに渡したものと同じであることを示しています。 - 後で
createAction()
別のペイロードで呼び出すと、出力System.out.println()
される2つの値が異なります!特に、印刷された2行目は、常にステップ1で印刷されたものと同じ値を示しています。
私はこれを何度も繰り返すことができます。渡された実際のペイロードは異なるIDハッシュコードを取得し続けますが、(ラムダ内に)印刷された2行目は同じままです!最終的に何かがクリックされ、突然数は再び同じになりますが、その後同じ動作で分岐します。
Javaはどういうわけかラムダとラムダに行く引数をキャッシュしていますか?しかし、これはどのように可能ですか?payload
引数がマークされfinal
、しかも、ラムダキャプチャは効果的に最終とにかくする必要が!
- キャプチャされた変数が数ラムダの深さである場合、ラムダがキャッシュされるべきでないことを認識しないJava 8バグはありますか?
- スレッド全体でラムダとラムダ引数をキャッシュするJava 8バグはありますか?
- または、ラムダキャプチャメソッドの引数とメソッドローカル変数について理解できないことはありますか?
最初に失敗した回避策の試み
昨日、メソッドスタックでメソッドパラメータをローカルにキャプチャするだけでこの動作を防ぐことができると思いました。
private SwingAction createAction(final Data payload) {
final Data theRealPayload = payload;
System.out.println(System.identityHashCode(theRealPayload));
return new SwingAction(() -> {
System.out.println(System.identityHashCode(theRealPayload));
//do stuff
//show an "are you sure?" modal dialog and get a response
//show a file selection dialog
//when the dialog completes, create a worker and show a status:
showStatusDialogWithWorker(() -> new SwingWorker() {
protected String doInBackground() {
save(theRealPayload);
}
});
その1行でData theRealPayload = payload
、突然ではtheRealPayload
なく今後使用した場合はpayload
、バグは発生しなくなりましcreateAction()
た。1 回呼び出すたびに、2つの出力行は、キャプチャされた変数のまったく同じインスタンスを示しています。
しかし、今日、この回避策は機能しなくなりました。
個別のバグ修正で問題に対処。しかし、なぜ?
内部で例外をスローしていた別のバグを見つけましたshowStatusDialogWithWorker()
。基本的にshowStatusDialogWithWorker()
は(渡されたラムダで)ワーカーを作成し、ワーカーが完了するまでステータスダイアログを表示することになっています。ワーカーを正しく作成するが、ダイアログの作成に失敗し、バブルアップしてキャッチされない例外をスローするバグがありました。このバグを修正して、showStatusDialogWithWorker()
ワーカーが実行されているときにダイアログが正常に表示され、ワーカーが終了したらダイアログを閉じるようにしました。ラムダキャプチャの問題を再現できなくなりました。
しかし、なぜ内部の何かshowStatusDialogWithWorker()
が問題に関係しているのでしょうか?私がプリントアウトされたときにSystem.identityHashCode()
外側と内側ラムダ、および値が異なった、これが起こっていたの前に showStatusDialogWithWorker()
呼ばれていた、と例外の前に送出されていました。後の例外で違いが生じるのはなぜですか?
その上、根本的な質問が残っています:final
メソッドによって渡され、ラムダによってキャプチャされたパラメーターが変更される可能性はありますか?
final
メソッドの引数のアイデンティティはラムダの外側と内側でどのように変化するでしょうか?また、変数をfinal
スタック上のローカル変数としてキャプチャすると、なぜ違いが生じるのでしょうか。
final
変数をスタック上の変数としてキャプチャしても、問題は修正されなくなりました。私は調査を続けています。
SwingAction
それ自体から来ているのではないかと疑いを抱いています。SwingAction
メソッドから返されたものをどうしますか?