ArrayList
でadd(obj)
を呼び出す複数のスレッドがあります。
私の理論では、add
が2つのスレッドによって同時に呼び出されると、追加される2つのオブジェクトのうちの1つだけがArrayList
に実際に追加されます。これはもっともらしいですか?
もしそうなら、どうやってこれを回避しますか? Vector
?などの同期コレクションを使用します
ArrayListの2つのスレッドによってaddが同時に呼び出されたときに何が起こるかについて、保証された動作はありません。ただし、両方のオブジェクトが正常に追加されたことは私の経験です。リストに関連するスレッドセーフの問題のほとんどは、追加/削除中の反復を処理します。それにもかかわらず、複数のスレッドと同時アクセスでVanilla ArrayListを使用しないことを強くお勧めします。
以前はベクターは並行リストの標準でしたが、現在は Collections synchronized list を使用することが標準になっています。
また、Javaでスレッドを操作することに時間を費やす場合は、GoetzなどによるJava Concurrency in Practice.
さまざまなことが起こり得ます。両方のオブジェクトを正しく追加できます。追加されたオブジェクトの1つだけを取得できます。基になる配列のサイズが適切に調整されなかったため、ArrayIndexOutOfBounds例外が発生する可能性がありました。または、他のことが起こるかもしれません。発生する動作に依存することはできないと言うだけで十分です。
代替として、Vector
を使用できます。Collections.synchronizedList
、CopyOnWriteArrayList
を使用することも、別のロックを使用することもできます。それはすべて、あなたが他に何をしているのか、そしてコレクションへのアクセスをどのように制御できるのかによって異なります。
null
、ArrayOutOfBoundsException
、または実装に残された何かを取得することもできます。 HashMap
sは、実稼働システムで無限ループに入ることが確認されています。何が間違っているのかを本当に知る必要はありません、ただそれをしないでください。
Vector
を使用することもできますが、インターフェイスが十分に充実していないことがわかります。ほとんどの場合、異なるデータ構造が必要になるでしょう。
実際のシナリオを多少模倣するために、次のコードを思いつきました。
100個のタスクが並行して実行され、完了ステータスがメインプログラムに更新されます。 CountDownLatchを使用して、タスクの完了を待ちます。
import Java.util.concurrent.*;
import Java.util.*;
public class Runner {
// Should be replaced with Collections.synchronizedList(new ArrayList<Integer>())
public List<Integer> completed = new ArrayList<Integer>();
/**
* @param args
*/
public static void main(String[] args) {
Runner r = new Runner();
ExecutorService exe = Executors.newFixedThreadPool(30);
int tasks = 100;
CountDownLatch latch = new CountDownLatch(tasks);
for (int i = 0; i < tasks; i++) {
exe.submit(r.new Task(i, latch));
}
try {
latch.await();
System.out.println("Summary:");
System.out.println("Number of tasks completed: "
+ r.completed.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
exe.shutdown();
}
class Task implements Runnable {
private int id;
private CountDownLatch latch;
public Task(int id, CountDownLatch latch) {
this.id = id;
this.latch = latch;
}
public void run() {
Random r = new Random();
try {
Thread.sleep(r.nextInt(5000)); //Actual work of the task
} catch (InterruptedException e) {
e.printStackTrace();
}
completed.add(id);
latch.countDown();
}
}
}
アプリケーションを10回実行し、少なくとも3〜4回実行すると、プログラムは完了したタスクの正しい数を印刷しませんでした。理想的には、100を印刷する必要があります(例外が発生しない場合)。しかし、場合によっては98、99などを印刷していました。
したがって、ArrayListの同時更新では正しい結果が得られないことがわかります。
ArrayListをSynchronizedバージョンに置き換えた場合、プログラムは正しい結果を出力します。
arrayListのスレッドセーフバージョンが必要な場合は、List l = Collections.synchronizedList(new ArrayList());
を使用できます。
ArrayListはスレッドセーフではないため、動作はおそらく未定義です。イテレータがリストを処理している間にリストを変更すると、ConcurrentModificationExceptionが発生します。 ArrayListをCollection.synchronizedListでラップするか、スレッドセーフなコレクション(多数あり)を使用するか、add呼び出しを同期ブロックに入れるだけです。
Java.util.concurrentにはスレッドセーフな配列リストがあります。標準のArrayListはスレッドセーフではなく、複数のスレッドが同時に更新されるときの動作は未定義です。また、1つ以上のスレッドが同時に書き込みを行う場合、複数のリーダーで奇妙な動作が発生する可能性があります。
ArrayList();
の代わりに使用できます:
Collections.synchronizedList( new ArrayList() );
または
new Vector();
synchronizedList
は、次の理由から望ましいです:
http://Java.Sun.com/j2se/1.4.2/docs/api/Java/util/ArrayList.html
この実装は同期されないことに注意してください。複数のスレッドがArrayListインスタンスに同時にアクセスし、少なくとも1つのスレッドが構造的にリストを変更する場合、外部で同期する必要があります。
内部的には同期が行われないため、理論化することは妥当ではありません。
したがって、物事は同期しなくなり、不快で予測不可能な結果になります。