web-dev-qa-db-ja.com

Collections.synchronizedList()メソッドの使用法は何ですか?リストを同期していないようです

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()が同期を行わないのですか?

19
Fullstack Guy

同期リストは、このリストのメソッドのみを同期します。

つまり、別のスレッドが現在このリストのメソッドを実行している間、スレッドはリストを変更できません。メソッドは処理中にオブジェクトがロックされます。

例として、2つの異なるリスト(A=A1,A2,A3B=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つが完全なループを実行してから、他のスレッドがリストにアクセスできるようにします。

21
jhamon

Collections.synchronizedList()は、オブジェクトのモニターとして同期リストインスタンスを使用して、同期ブロック内で実行する必要がある反復処理中を除き、バッキングリストへのすべてのアクセスを同期します。

たとえば、ここにaddメソッドのコードがあります

public boolean add(E e) {
    synchronized (mutex) {return c.add(e);}
}

これにより、バックアップされたリストへのシリアルアクセスが保証されるため、2つのスレッドがaddを同時に呼び出すと、1つのスレッドがロックを取得し、その要素を追加してロックを解除し、2番目のスレッドがその要素をロックして追加するのは、出力でonetwoを交互に取得する理由です。

同期ブロックのコメントを解除すると、コードは

synchronized(o) {
    for(int i=0;i<100;i++){
        ...
    }
}

この場合、oのロックを取得できるスレッドは、最初にentireforループを実行してからロックを解除します(例外がスローされる場合を除く)。同期ブロックのコンテンツを実行する他のスレッド。これが100連続した時間oneまたはtwoその後100連続して他の値を掛けます。

10
Nicolas Filotto

観察可能な動作は絶対に正しいです-コードサンプルで示すsynchronizedアプローチは、synchronizedListと同じではありません。最初のケースでは、for文全体を同期するため、1つのスレッドのみが同時にそれを実行します。 2番目のケースでは、コレクションメソッド自体をシンクロナイズします-それがsynchronizedListの略です。 addメソッドは同期されていることを確認してください-forメソッドは同期されていません!

2
bsiamionau

以前の回答によると、アクセススレッドsynListtOneから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();
}

}

0
e2a