2016/07/12

[java]非同期に翻弄される

人間、いろいろあると、不信感に襲われてしまいますな、いやいや。

状態変数のチェックをメソッドの始まりで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 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。