Magicode logo
Magicode
4 min read

プログラムは思ったとおりには動かない プログラムは書いたとおりには動かない

https://cdn.apollon.ai/media/notebox/security-g1f8e5bff6_1280.jpg
プログラミングというものは書いた通りには動きません。
たとえばスレッドを二つ立ち上げて、スレ1は1を、スレ2は-1を入れるような処理を描きます。
java
public class magicode
{
  // 64ビットのロングなバリュー
  private static long longValue = 0;
  
  public static void main(String[] args) throws Exception
  {
    // じゅうおくかいくりかえす
    final int LOOP = 1000 * 1000 * 1000;
    
    // longなバリューに いち を入れるスレッドさくせい
    Thread th1 = new Thread(new Runnable()
    {
      public void run()
      {
        for (int i = 0; i < LOOP; i++)
        {
          longValue = 1;
          check(longValue); // 1 || -1 ならOK
        }
      }
    });
    
    // longなバリューに マイナスいち を入れるスレッド作成
    Thread th2 = new Thread(new Runnable()
    {
      public void run()
      {
        for (int i = 0; i < LOOP; i++)
        {
          longValue = -1;
          check(longValue); // 1 || -1 ならOK
        }
      }
    });
    
    // ほぼ同時に処理スタート
    th1.start();
    th2.start();
    
    // 両方のスレッドが終わるのを待つ・・・
    th1.join();
    th2.join();
    
    System.out.println("Finished");
  }
  
  // 1と-1以外になった場合には例外を発生させる
  // 書いた通りに動くなら例外は発生しないはず
  private static void check(long value)
  {
    if (value != 1 && value != -1)
    {
      // nanigaderukana nanigaderukana
      throw new RuntimeException(String.valueOf(value));
    }
  }
}
これを実行すると、64ビットjava8だと思った通りの動作をしますが
32ビットjava8だとたまにこうなります。
Exception in thread "Thread-0" java.lang.RuntimeException: 4294967295
        at magicode.check(magicode.java:63)
        at magicode.access$100(magicode.java:11)
        at magicode$1.run(magicode.java:28)
        at java.lang.Thread.run(Thread.java:745)
Finished
Exception in thread "Thread-1" java.lang.RuntimeException: -4294967295
        at magicode.check(magicode.java:63)
        at magicode.access$100(magicode.java:11)
        at magicode$1.run(magicode.java:28)
        at java.lang.Thread.run(Thread.java:745)
Finished
1と-1しか入れてないのに例外として
4294967295 や -4294967295 などが返却されてます。
これはなにかというと
4294967295は 「0000 0000 FFFF FFFF」
-4294967295は 「FFFF FFFF 0000 0001」
です。
ちなみに
1は「0000 0000 0000 0001」
-1は「FFFF FFFF FFFF FFFF」
です。
つまり、なにがおこっているのか図で描くとこうなります。
mem
32ビットアーキテクチャで64ビットの領域を扱う場合
32ビットの領域を2つ確保して扱います。
その2つの領域へ値を入れる場合、1回の代入の際に上位ビットと下位ビットを順序よく入れるのですが
タイミングよく2つのスレッドが同時にいれようとするとたまにクロスします。
これがもしも金額計算だったらやばいですよね。
なので、こういったことが起きないように
並行プログラミングする際にはvolatailをつけたりjava.util.concurrent.atomicを使ったり(内部的には同じものらしい) します。
こういうことをいわゆるスレッドセーフといいます。
まぁ今はあんまり意識しないですかね。

Discussion

コメントにはログインが必要です。