2つのスレッドを使用してString
値をArrayList
に追加しようとしています。私が欲しいのは、1つのスレッドが値を追加している間、他のスレッドが干渉してはならないため、_Collections.synchronizedList
_メソッドを使用したことです。しかし、オブジェクトで明示的に同期しない場合、追加は非同期の方法で行われるようです。
明示的な同期ブロックなし:
_public class SynTest {
public static void main(String []args){
final List<String> list=new ArrayList<String>();
final List<String> synList=Collections.synchronizedList(list);
final Object o=new Object();
Thread tOne=new Thread(new Runnable(){
@Override
public void run() {
//synchronized(o){
for(int i=0;i<100;i++){
System.out.println(synList.add("add one"+i)+ " one");
}
//}
}
});
Thread tTwo=new Thread(new Runnable(){
@Override
public void run() {
//synchronized(o){
for(int i=0;i<100;i++){
System.out.println(synList.add("add two"+i)+" two");
}
//}
}
});
tOne.start();
tTwo.start();
}
}
_
私が得た出力は次のとおりです。
_true one
true two
true one
true two
true one
true two
true two
true one
true one
true one...
_
明示的な同期ブロックのコメントを外した状態で、追加中に他のスレッドからの干渉を停止しています。スレッドがロックを取得すると、終了するまで実行されます。
同期ブロックのコメントを外した後のサンプル出力:
_true one
true one
true one
true one
true one
true one
true one
true one...
_
では、なぜCollections.synchronizedList()
が同期を行わないのですか?
同期リストは、このリストのメソッドのみを同期します。
つまり、別のスレッドが現在このリストのメソッドを実行している間、スレッドはリストを変更できません。メソッドは処理中にオブジェクトがロックされます。
例として、2つの異なるリスト(A=A1,A2,A3
とB=B1,B2,B3
)をパラメーターとして、2つのスレッドがリストでaddAll
を実行するとします。
メソッドが同期されると、これらのリストがA1,B1,A2,A3,B2,B3
のようにランダムにマージされないことが確実になります。
スレッドがいつ他のスレッドにプロセスを引き継ぐかは決定しません。各メソッド呼び出しは、他のメソッド呼び出しを実行する前に完全に実行して戻る必要があります。したがって、A1,A2,A3,B1,B2,B3
またはB1,B2,B3,A1,A2,A3
のいずれかを取得できます(最初に実行されるスレッド呼び出しがわからないため)。
最初のコードでは、両方のスレッドが同時に実行されます。そして、両方ともリストの要素をadd
しようとします。 add
メソッドの同期を除き、1つのスレッドをブロックする方法はないため、プロセスをスレッド2に渡す前に、スレッド1が複数のadd
操作を実行することを妨げるものはありません。まったく正常です。
2番目のコード(コメントなしのコード)では、ループを開始する前に、スレッドが他のスレッドからのリストを完全にロックすることを明確に述べています。したがって、スレッドの1つが完全なループを実行してから、他のスレッドがリストにアクセスできるようにします。
Collections.synchronizedList()
は、オブジェクトのモニターとして同期リストインスタンスを使用して、同期ブロック内で実行する必要がある反復処理中を除き、バッキングリストへのすべてのアクセスを同期します。
たとえば、ここにadd
メソッドのコードがあります
public boolean add(E e) {
synchronized (mutex) {return c.add(e);}
}
これにより、バックアップされたリストへのシリアルアクセスが保証されるため、2つのスレッドがadd
を同時に呼び出すと、1つのスレッドがロックを取得し、その要素を追加してロックを解除し、2番目のスレッドがその要素をロックして追加するのは、出力でone
とtwo
を交互に取得する理由です。
同期ブロックのコメントを解除すると、コードは
synchronized(o) {
for(int i=0;i<100;i++){
...
}
}
この場合、o
のロックを取得できるスレッドは、最初にentirefor
ループを実行してからロックを解除します(例外がスローされる場合を除く)。同期ブロックのコンテンツを実行する他のスレッド。これが100
連続した時間one
またはtwo
その後100
連続して他の値を掛けます。
観察可能な動作は絶対に正しいです-コードサンプルで示すsynchronized
アプローチは、synchronizedList
と同じではありません。最初のケースでは、for文全体を同期するため、1つのスレッドのみが同時にそれを実行します。 2番目のケースでは、コレクションメソッド自体をシンクロナイズします-それがsynchronizedList
の略です。 add
メソッドは同期されていることを確認してください-for
メソッドは同期されていません!
以前の回答によると、アクセススレッドsynList
とtOne
からtTwo
を同期する必要があります。この場合、監視パターンを使用して、セキュリティアクセスを提供できます-スレッドのために。
以下では、同じ問題を抱えている他のユーザーと共有するようにコードを調整しました。このコードでは、synList
のみを使用して、同期した方法でアクセスを制御しました。 synListからの注文アクセスを保証するために他のオブジェクトを作成する必要はないことに注意してください。この質問を補完するために、本を読んでくださいJava Concurrency in Practice jcip 第4章では、Hoareの研究に触発されたモニター設計パターンについて説明しています。
public class SynTest {
public static void main(String []args){
final List<String> synList= Collections.synchronizedList(new ArrayList<>());
Thread tOne=new Thread(() -> {
synchronized (synList) {
for (int i = 0; i < 100; i++) {
System.out.println(synList.add("add one" + i) + " one");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread tTwo=new Thread(()->{
synchronized (synList) {
for(int i=0;i<100;i++){
System.out.println(synList.add("add two"+i)+" two");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
tOne.start();
tTwo.start();
}
}