BLEのサンプルソースを見ていたら、こういう記述があった。
if (!mBluetoothAdapter.isEnabled()) {
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);return;
}}
Bluetooth設定が有効になっているかどうかをチェックしているのだが、同じチェックを2回やっている。
C/C++のときは、シングルトンの取得なんかでやってたような記憶がある。
マルチスレッド環境で、最初がfalseになって次の処理を行うまでの間にディスパッチが発生して、ディスパッチ先でもfalseになるからそっちで最初のインスタンスを生成し、またディスパッチが発生して戻ってくるけど、戻った人はそんなの知らないから同じようにインスタンスを作ってしまう、というようなストーリーだったと思う。
考えるのが面倒だったので、newで生成せずにstatic変数にしていたのだが、Javaも同じようなことだろうか?
MSC07-J. シングルトンオブジェクトのインスタンスを複数作らない
取得する関数をsynchronizedにしてしまう(synchronizedメソッド)か、1回目のnullチェック後をsynchronizedする(ダブルチェックロック手法)か、あるいはstaticな内部クラスを持たせる(Initialize-on-Demand Holderクラスパターン)かのようだ。
いろいろあるねぇ。
if文だけでチェックすると、2回目のif文でもnullだったとして、その次のインスタンスを生成するまでの間に割り込まれてしまうかもしれない。
何度if文だけを繰り返しても「その間に割り込まれたらどうする?」が発生するので、こういう言語レベルで同期処理が保証されるというのは実装しやすそうだ。
しかし、if文のあととsynchronizedの前に割り込まれるということはないのだろうか?
Practical Javaからの抜粋:インスタンス・メソッドの場合、synchronizedキーワードはオブジェクトをロックし、メソッドやコードをロックしないということを理解する
「インスタンスメソッド」は、オブジェクトごとというかインスタンスごとというか、thisが使えるメソッド。
その反対はクラスメソッドで、いわゆるstaticのついたメソッドのこと。
なので、ここで言いたいのは、staticじゃない関数でsynchronizedを使った場合は、クリティカルセクションみたいな動きをするんじゃなくて、値の変更に相当する処理だけロックしますよ、ということなのだろう。
と思ったが、違うみたい。
最初にsynchronizedでオブジェクトαを取得したAさんがいるとして、もしオブジェクトβがsynchronizedなことを使用としたとしても、αとβは別物だからロックはされない。
そして、オブジェクトの中身を使おうと使うまいと、同じオブジェクトから実行するsynchronizedメソッドはロックされてしまう、ということのようだ。
Effective Javaにもいくつか書いてあるし、「LCK10-J. ダブルチェックロック手法を誤用しない」というページもあるので、思っていない動作をしたくないなら、気をつけた方がよいだろう。
でも「Androidアプリの場合」という前提がつくと、省略できるパターンがあったりするのかもしれない。
たぶんこれミスですよね・・・きっと・・・。
返信削除synchronized付け忘れ、というよりも、if文は2つもいらんよ、という方ですかね・・・。
削除深読みしすぎたかなぁ。