2009/05/07
Javaのオートボクシングでバグ寸前
Javaの同期処理の説明のために以下のようなコードを使いました。synchronizedを説明するため以外に意味のあるコードではありません。
class MyWorker implements Runnable { private static final int LOOP_NUM = 10000000; private MyCounter counter; MyWorker(MyCounter counter) { this.counter = counter; } @Override public void run() { for (int i = 0; i < LOOP_NUM; i++) { counter.increment(); } } } public class MyCounter { private int count; public synchronized void increment() { count++; } public void doit() throws InterruptedException { Thread t1 = new Thread(new MyWorker(this)); t1.start(); Thread t2 = new Thread(new MyWorker(this)); t2.start(); t1.join(); t2.join(); System.out.println(count); } public static void main(String[] args) throws InterruptedException { MyCounter my = new MyCounter(); my.doit(); } }
この後、synchronizedには別の書き方もあるという説明をするため、countの型が参照型のIntegerなら次のようにも書ける、と展開しようと思いました。
public class MyCounter { private Integer count = 0; public void increment() { synchronized(count) { count++; } } 以下省略
分かる人には一瞬でしょうが、これは期待どおりの同期処理をしません。答えを直接書くのは野暮なので、分からない人にヒントだけ書きます。Integerは不変型なのでcount++でオブジェクトの状態を変更していません。
以前(http://dev.ariel-networks.com/Members/inoue/terror-bugs2)もオートボクシングで恐いバグを経験しましたが、オートボクシングはJavaに気づきにくいバグをもたらします。
FindBugsを通してみると、 http://findbugs.sourceforge.net/bugDescriptions.html#DL_SYNCHRONIZATION_ON_BOXED_PRIMITIVE
DL: Synchronization on boxed primitive could lead to deadlock
の警告を出してきました。
もしふたつのスレッドが次のコードを実行するとデッドロックする可能性があります。まあ、こんなコードは書きませんが。
// スレッド1 Integert i1 = 1; synchronized(i1) { increment(); } // スレッド2(MyCounterクラスのメソッドを仮定) Integert i0 = 0; synchronized(i0) { count = 1; increment(); }
Javaで試験問題を作る時はFindBugsを参考にするのがよさそうです。
- Category(s)
- カテゴリなし
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/inoue/terror-bugs3/tbping