[Java] マルチスレッドでjava.util.ArrayListオブジェクトが同期できていないことを示す例
例
以下のように、ひとつのjava.util.ArrayListオブジェクトに対して、複数のスレッドからオブジェクトをaddメソッドで追加するようにします。そうすると、すべてのスレッドが終了した後、ArrayListオブジェクトのsizeメソッドで得られる値が、期待したものとは異なることがあります。
import java.util.ArrayList; class TestArrayList { public static void main(String[] arg) { new TestArrayList().test(); } private void test() { // 複数のスレッドから同時アクセスされるオブジェクト ArrayList<Object> list = new ArrayList<Object>(1); // スレッドを実行する。 int threads_count = 10; int add_count = 5000; Thread[] threads = new Thread[threads_count]; for (int i = 0; i < threads.length; i++) { Thread thread = new Thread(new Appender(list, add_count)); thread.start(); threads[i] = thread; } // スレッドの終了を待つ。 for (int i = 0; i < threads.length; i++) { try { threads[i].join(); } catch (InterruptedException e) { System.out.println(e); } } // 結果を表示する。 System.out.println("size=" + Integer.toString(list.size()) + ", expected=" + Integer.toString(threads_count * add_count)); } /** * スレッドで実行されるRunnableクラス。 */ private class Appender implements Runnable { private ArrayList<Object> array; private int add_count; public Appender(ArrayList<Object> array, int add_count) { this.array = array; this.add_count = add_count; } public void run() { for (int i = 0; i < this.add_count; i++) { this.array.add(new Object()); } } } }
以下のスクリプトで期待通りの値にならない回数を数えたところ、おおよそ50%の確率でこの現象が発生しました。
yes | head -100 | while read foo do java TestArrayList done 2>/dev/null | perl -n -e 'if (m/^size=(.*), expected=(.*)$/) { if ($1 ne $2) { print; } }' | wc -l
また、以下のような例外が発生することがありました。
Exception in thread "Thread-9" java.lang.ArrayIndexOutOfBoundsException at java.lang.System.arraycopy(Native Method) at java.util.ArrayList.ensureCapacity(ArrayList.java:170) at java.util.ArrayList.add(ArrayList.java:351) at TestArrayList$Appender.run(TestArrayList.java:47) at java.lang.Thread.run(Thread.java:595)
ArrayListオブジェクトの代わりに、メソッドがsynchronizedとなっているjava.util.Vectorオブジェクトを使うと、この現象は発生しませんでした。