2011/12/19

JavaのレガシーなCollectionクラスでもスレッドセーフではない?

JDK/JRE 1.x時代から存在するjava.util.Vectorなどのレガシーなクラスは、Thread-safe(スレッドセーフ)です。
これにも関わらず、思うような結果にならないケースがあります。
次は、検証用のサンプル(Java SE 5.0以降)です。
package a;

import java.util.Arrays;
import java.util.List;
import java.util.Vector;

class A implements Runnable {
 //VectorはThread-safeである
 private List<String> v = new Vector<String>();
 
 public void run() {
  for(int i=0; i<1000; i++){
   //3桁の数字文字列を生成
   final String s = createNumberString(i);
   //文字列sがVectorに含まれていなければ、追加する
   if(v.contains(s) == false){
    v.add(s);
   }
  }
 }

 //3桁の文字列を生成する(例:1→"001")
 private static String createNumberString(int i){
  final StringBuilder sb = new StringBuilder(String.valueOf(i));
  while(sb.length() < 3){
   sb.insert(0, "0");
  }
  return sb.toString();
 }

 public static void main(String[] args) throws InterruptedException {
  //多重度3でA.run()の処理を実行する
  final A a = new A();
  final Thread[] tt = new Thread[3];
  for(int i=0; i<tt.length; i++){
   tt[i] = new Thread(a);
   tt[i].start();
  }
  for(Thread t : tt){
   t.join();
  }

  //処理結果を出力
  final List<String> v = a.v;
  final String[] ss = v.toArray(new String[v.size()]);
  Arrays.sort(ss);
  for(String s : ss){
   System.out.println(s);
  }
 }
}
上記プログラムでは、実行するたびに結果が変わりますが、次のように同じ数字が2か3個出力されるでしょう。
各数字がきれいに1個ずつ出力されないのです。
000
001
002
 :
169
169
170
170
171
171
172
172
173
173
 :
999
VectorがThread-safeなのは、Vectorインスタンス内部に限ります。つまりVectorインスタンスの内部ではデータの整合性は保たれますが、Vectorを使う側(上記プログラムではAクラス)では必ずしもデータを正しく操作できることを保証するものではありません。
簡単な図解を作りました。


To use the legacy Collection is not necessarily thread-safe.

次のように、Vectorインスタンスにアクセスする範囲全体を排他する必要があります。
 public void run() {
  final List<String> v = this.v;
  for(int i=0; i<1000; i++){
   //3桁の数字文字列を生成
   final String s = createNumberString(i);
   synchronized(v){
    //文字列sがVectorに含まれていなければ、追加する
    if(v.contains(s) == false){
     v.add(s);
    }
   }
  }
 }
なお今回のようなケースではAクラスでのsynchronizedとVector内部でのsynchronizedとロックが二重になりますから、スレッドセーフではないjava.util.ArrayListを使う方が、速度性能が良くなりますね。

0 件のコメント:

コメントを投稿