人間、いろいろあると、不信感に襲われてしまいますな、いやいや。
状態変数のチェックをメソッドの始まりで2回やって、そこまでチェックをくぐり抜けたら処理する、とやってたのだけど、すり抜けるのをはじめて目にしてしまってね。
もう何日もテストして無事だったのに、なんでいまさらーっ?
タイトルからわかるように、非同期で状態変数を変更する処理が呼ばれていたのですな。
メソッド() {
if (状態変数のチェック1) {
return 成功でよい;
}
//★
if (状態変数のチェック2) {
return 失敗と見なす;
}
実行();
}
別メソッド(setVal) {
状態変数 = setVal;
}
★のところでディスパッチして、別メソッドが呼ばれて、チェック1で引っかかるような値が代入されてしまったのですよ。
チェック1で引っかかる値はチェック2でも引っかかるので、先にチェック1で見て、既にこの状態だったらOK、ということだったのだ。
いやいや、そのタイミングでディスパッチできるとは、やりますな。
とまあ、教科書にあるような失敗例だったので、教科書にあるような対処法で済む。
Javaにはsynchronizedという便利なものがあるので、それを使えばよいだろう。
例では短く書いているけど、本当はもっと長くて、メソッド自体をsynchronizedしてしまうとデッドロックしてしまうのだ。
なので、「実行()」の手前までディスパッチを禁止してくれれば良いのだ。
ただ、そんなにひどいことをさせられないのか、synchronized(オブジェクト)、みたいな書き方なのですな。
「このオブジェクトに掛けて、排他してやる!」みたいな意気込みになっているように思える。
finalじゃないといかんようだ。
まあ、どんどんオブジェクトが変わってしまうと排他にならないからか。
final Object lockObj = new Object();
メソッド() {
synchronized(lockObj) {
if (状態変数のチェック1) {
return 成功でよい;
}
//★
if (状態変数のチェック2) {
return 失敗と見なす;
}
}
実行();
}
別メソッド(setVal) {
synchronized(lockObj) {
状態変数 = setVal;
}
}
で、問題はどちらかというとこれからで、本当にこの対策で現象が出なくなるのか?だ。
実は別の場所の方に問題があって、これではだめなのかもしれない。
しかし、再現性が元々かなり低いので、自然に発生するのを待つことはできないだろう。
どうするかというと、割込が入りやすいように、★のところにThread.sleep()を入れた。
まず、synchronizedを外した状態で動かして、発生していたエラーと同じことが起きることを期待する。
そして、synchronizedを付けて、エラーが起きないことを期待する。
うん、今回はうまくいったようだ。
ただねぇ、作りをあれこれ変えていったので、実はこういうチェック無しでもいけるんじゃないの?という気がしている。
その決断を下す勇気がないというか、判断ができないというかで、現状維持しているというところ。
ここら辺は、付け焼き刃の知識じゃダメだなー、と思う。
空気感が読めないというのかね。
0 件のコメント:
コメントを投稿
コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。
注: コメントを投稿できるのは、このブログのメンバーだけです。