0

昔のCS時代を思い出そうとしています。

可能な限り低いプリミティブで、同期されたスレッドのペアを適切に実装しようとしています。もちろん、本番コードではより優れた同時実行ツールを使用する必要があります (おそらく java.util.concurrency のもの)。でもねえ、私は挑戦のためにこれをやっています。これが私のコードです(これは私の最初の質問なので、これが長すぎる場合はご容赦ください):

public class Test {

    public volatile Object locker1 = new Object();
    public volatile Object locker2 = new Object();
    public volatile Object locker3 = new Object();

    public class MyRunnable2 implements Runnable {
        public void run() {

            System.out.println( "MyRunnable2 started" );



            synchronized( locker3 ) {
                    try {
                        System.out.println( "r2: waiting for locker3" );
                        locker3.wait();
                        System.out.println( "r2: got locker3" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
            }



            for ( int c = 0; c < 50; ++c ) {

                synchronized( locker2 ) {

                    try {
                        System.out.println( "r2: waiting for locker2" );
                        locker2.wait();
                        System.out.println( "r2: got locker2" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
                }

                System.out.println( "r2: " + ( c ) );
                try {
                    Thread.sleep(1);
                } catch ( Exception e ) {
                }

                synchronized( locker1 ) {
                    System.out.println( "r2: signaling locker1" );
                    locker1.notify();
                    System.out.println( "r2: locker1 signaled" );
                }
            }
        }
    }

    public class MyRunnable1 implements Runnable {
        public void run() {
            System.out.println( "MyRunnable1 started" );

            synchronized( locker3 ) {
                    try {
                        System.out.println( "r1: waiting for locker3" );
                        locker3.wait();
                        System.out.println( "r1: got locker3" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
            }

            for ( int c = 0; c < 50; ++c ) {


                synchronized( locker1 ) {

                    try {
                        System.out.println( "r1: waiting for locker1" );
                        locker1.wait();
                        System.out.println( "r1: got locker1" );
                    } catch ( java.lang.InterruptedException e ) {
                        System.out.println( "e: " + e );
                    }
                }

                System.out.println( "r1: " + ( c ) );
                try {
                    Thread.sleep(1);
                } catch ( Exception e ) {
                }

                synchronized( locker2 ) {
                    System.out.println( "r1: signaling locker2" );
                    locker2.notify();
                    System.out.println( "r1: locker2 signaled" );
                }

            }
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.test();
    }

    public void test() {
        MyRunnable1 r1 = new MyRunnable1();
        MyRunnable2 r2 = new MyRunnable2();
        Thread t1 = new Thread( r1 );
        Thread t2 = new Thread( r2 );
        t1.start();
        t2.start();

        try {
            Thread.sleep(1000);
        } catch ( Exception e ) {

        }
        synchronized( locker3 ) {
            System.out.println( "main: signaling locker3" );
            locker3.notifyAll();
            System.out.println( "main: locker3 signaled" );
        }

        try {
            Thread.sleep(1000);
        } catch ( Exception e ) {

        }

        synchronized( locker1 ) {
            System.out.println( "main: signaling locker1" );
            locker1.notify();
            System.out.println( "main: locker1 signaled" );
        }


        try {
            t1.join();
            t2.join();


        } catch ( java.lang.InterruptedException e ) {
            System.out.println( "e: " + e );
        }
    }
}

私の質問は次のとおりです: Test.test() で競合状態を回避するにはどうすればよいですか? ほとんどの場合、これは機能しますが、スリープ コールには満足できません。また、私のスタイルを評価してください。私は常に自己改善にオープンです。

編集:わかりやすくするために。MyRunnable1 を常に最初に実行したい。数値を出力してから、MyRunnable2 が同じ数値を出力するのを待ちます。次に、2 番目の数値を出力し、MyRunnable2 を再び待ちます。等々。

内部で何が起こっているかを知るまで、java.util.concurrency を快適に使用することはできないと思います。

4

2 に答える 2

1

これには、スリープ コール以外にもいくつかの基本的な問題があります (実際、スリープ コールには本質的に問題はありません...)。

まず第一に、スレッドが互いにシグナルを送るために実際には何もしていません。私があなたのコメントを正しく理解しているなら、あなたは次のようなものが欲しい

Thread 1: 1
Thread 2: 1
Thread 1: 2
Thread 2: 2
...

出力として。このコードが現在行っていることは、両方のスレッドを開始し、両方のスレッドが待機することです。次に、メイン スレッドがオブジェクトを呼び出しnotifyAllますlocker3。つまり、そのオブジェクトを待機しているスレッドはすべて実行されますが、すべての人に通知しているため、スレッドが実行される順序は保証されません。競合状態番号 1 があります。さらに、andオブジェクトを 1 回しか呼び出しnotifyAllていませんが、スレッドごとに約 50 回待機することになります。つまり、スレッドがハングアップします。locker2locker1

本当に必要なのは次のようなものです。

  1. スレッド 1 とスレッド 2 が開始されます
  2. スレッド 2 はすぐに何らかのシグナル オブジェクトを待機します。
  3. スレッド 1 はすぐに数値を出力し、同じシグナル オブジェクトで notify を呼び出します。
  4. スレッド 1 は、同じシグナル オブジェクトを待機します。
  5. スレッド 2 が再開され、番号が出力されます
  6. スレッド 2 は、シグナル オブジェクトで通知を呼び出します。
  7. スレッド 2 は、シグナル オブジェクトを待機します。
  8. 手順 3 に戻ります。

競合状態がまったくないことを保証することはできませんが、あなたが提案していることを行うには、そのようなアルゴリズムが必要になります. 任意にもっと複雑にすることもできますが、それはちょっと良いベースラインです.

于 2012-10-20T04:08:44.630 に答える
1

@Chris Thompsonは正しいです-単一の信号オブジェクトを交互に使用できます。ただし、どのスレッドが最初に実行されるかを保証することはできません。最後から 2 番目のスレッドが既に通知された後に、最後から 2 番目のスレッドが通知するのを最後のスレッドが待機していないように注意する必要があります。終了し、終了しました。

コードを修正して動作させましたが、どちらが先かという保証はありません。次に、2 つのスレッドの実行順序を制御する別の「MyRunnableOrdered」も追加しました。どちらの場合でも、完了するループの数がスレッドにない場合、またはいずれかがエラーのために終了した場合は、枯渇する危険があります。中断された例外に注意して使用すると、後者の場合に役立ちます。

public class Test {

public Object locker = new Object();
public boolean oneDone = false;

public class MyRunnable2 implements Runnable {
    public void run() {

        System.out.println( "MyRunnable2 started" );

        for ( int c = 0; c < 50; ++c ) {
            synchronized( locker ) {
                System.out.println( "r2: " + ( c ) );
                locker.notify();
                if(c == 49) {
                    oneDone = true;
                }

                try {
                    if(!oneDone) {
                        locker.wait();
                    }
                } catch ( java.lang.InterruptedException e ) {
                    System.out.println( "e: " + e );
                }                    
            }
        }
    }
}

public class MyRunnable1 implements Runnable {
    public void run() {
        System.out.println( "MyRunnable1 started" );

        for ( int c = 0; c < 50; ++c ) {
            synchronized( locker ) {
                System.out.println( "r1: " + ( c ) );
                locker.notify();
                if(c == 49) {
                    oneDone = true;
                }

                try {
                    if(!oneDone) {
                        locker.wait();
                    }
                } catch ( java.lang.InterruptedException e ) {
                    System.out.println( "e: " + e );
                }
            }
        }
    }
}


public Object sequenceLock = new Object();
public boolean sequence = true;

public class MyRunnableOrdered implements Runnable {

    private final boolean _match;

    public MyRunnableOrdered(boolean match) 
    {
        _match = match;
    }

    public void run() {
        System.out.println( "MyRunnable1 started" );

        for ( int c = 0; c < 50; ++c ) {
            synchronized( sequenceLock ) {
                while(_match != sequence) {
                    try {
                        sequenceLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println( "r" + _match + ":" + ( c ) );
                sequence = !sequence;
                sequenceLock.notify();
            }
        }
    }
}    


public static void main(String[] args) {
    Test t = new Test();
    t.test();
}

public void test() {
    MyRunnable1 r1 = new MyRunnable1();
    MyRunnable2 r2 = new MyRunnable2();
    Thread t1 = new Thread( r1 );
    Thread t2 = new Thread( r2 );


    synchronized( locker ) {
        t1.start();
        t2.start();
    }


    try {
        t1.join();
        t2.join();
    } catch ( java.lang.InterruptedException e ) {
        System.out.println( "e: " + e );
    }

    System.out.println("Done part 1");

    MyRunnableOrdered o1 = new MyRunnableOrdered(true);
    MyRunnableOrdered o2 = new MyRunnableOrdered(false);
    synchronized(sequenceLock) {
        sequence = true;
    }
    Thread to1 = new Thread( o1 );
    Thread to2 = new Thread( o2 );
    to1.start();
    to2.start();

    try {
        to1.join();
        to2.join();
    } catch ( java.lang.InterruptedException e ) {
        System.out.println( "e: " + e );
    }       
    System.out.println("Done part 2");
}
}

MyRunnableOrdered のアイデアは、notify が呼び出されたときに誰が起動するかを制御しないため、2 つのスレッドを超えて拡張されないことに注意してください。その場合に必要なのは、処理するスレッドの順序付きリストです。その時点で、並行性は最善の解決策ではないかもしれません!

同時実行ライブラリを使用することにした場合は、おそらく AtomicBoolean を使用し、ロックを行わない MyRunnableOrdered のより良い実装もあるでしょう。

また、すべての変数アクセスは同期ブロックによって保護されているため、「volatile」を使用する必要がないことにも注意してください。

于 2012-10-22T18:04:26.393 に答える