1

問題は、2 つのスレッドがあり、1 つはリストへのライターであり、もう 1 つはリストからのリーダーです。ライターのループに大量の反復がある場合、リーダーがスタックすることがあります。その場合、そのリーダーはブロック (待機中ではない) になります。これは、通知を受信したことを意味しますが、ライターはモニターを解放しませんでしたか?

それで、なぜそうですか?これをどうするのが最善ですか?(睡眠は大丈夫ですか?)

import java.util.LinkedList;
import java.util.List;

public class Main {

    private List<Object> m_calls =  new LinkedList<Object>();

    public void startAll(){

        Thread reader = new Thread(new Runnable() {           
            @Override
            public void run() {
                while(true){
                    synchronized(m_calls){
                        while (m_calls.size() == 0) {
                            try {
                                System.out.println("wait");
                                m_calls.wait();
                            } catch (InterruptedException e) {                               
                                return;
                            }
                        }
                        m_calls.remove(0);
                        System.out.println("remove first");
                    }
                }
            }
        });

        Thread writer = new Thread(new Runnable() {           
            @Override
            public void run() {

                for(int i = 0; i < 15; i++){

                    // UN-comment to have more consistent behavior
                    /*try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }*/
                    synchronized(m_calls){
                        m_calls.add(new Object());
                        m_calls.notifyAll();
                        System.out.println("sent");
                    }
                }
            }
        });

        reader.start();
        writer.start();
    }

    public static void main(String[] args) {     
        new Main().startAll();       
    } 
}

上記のコードを実行すると、異なる結果が得られます。

---------------------------------- 1回目の試み

送信待ち
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み 送信 済み送信 済み送信 済み送信 済み送信済み
送信済み
送信済み
送信済み
送信 済み

















---------------------------------- 2回目の試み

送信待ち
送信 済み
送信 済み
送信 済み
送信 済み
送信 済み 最初の 削除 最初の 削除 最初 の 削除 最初 の 削除 最初 の 削除 送信 済み 送信 済み の 削除 最初 の 削除




























------------------------------ コメントなしの sleep() - 期待通りの動作


送信待ち 削除
最初の 送信
待ち 削除最初の 送信 待ち 削除最初の 送信 待ち 削除最初の 送信 待ち 削除最初の 送信 待ち削除最初の 送信 待ち削除 最初の 送信 待ち削除最初の 送信 待ち削除 最初の 送信 待ち削除 最初の 送信 待ち削除最初の 送信 待ち 削除最初 の 送信 待ち 最初に 削除する 送信を 待つ最初に削除する






































送信を 待つ
最初に削除
する

編集 1: リーダー スレッド (そのうちの 1 つ) はもう待機していないようで、むしろブロックされています。これは、モニターが通知を受信したように見えますが (notifyAll() の後)、ライター スレッドはループ内でロックを解放しません。 ...

ここに画像の説明を入力

4

3 に答える 3

3

あなたの特定の状況は、BlockingQueue. ブロッキング キューは、何かが (ライターによって) キューに入れられるtakeまで、スレッド (リーダー)をブロックします。put

ブロッキング キューを使用して変更したコードは次のとおりです。

public class Main {

    private BlockingQueue<Object> m_calls =  new LinkedBlockingQueue<Object>();

    public void startAll(){

        Thread reader = new Thread(new Runnable() {           
            @Override
            public void run() {
                while(!Thread.currentThread().isInterrupted()) {
                    try {
                        Object obj = m_calls.take();
                        System.out.println("obj taken");
                    } catch(InterruptedException ex) {
                        // Let end
                    }
                }
            }
        });

        Thread writer = new Thread(new Runnable() {           
            @Override
            public void run() {
                try {
                    for(int i = 0; i < 15; i++){
                        m_calls.put(new Object());
                        System.out.println("obj put");
                    }
                } catch (InterruptedException ex) {
                    // Let end
                }
            }
        });

        reader.start();
        writer.start();
    }

    public static void main(String[] args) {     
        new Main().startAll();       
    }
}

出力:

obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken
obj put
obj taken

LinkedListこれは、a) プレーンを使用し、b) 独自の待機/通知を使用しようとするよりもはるかに安全です。あなたの待機/通知も、競合状態に対して非常に脆弱でした。notifyリーダーが を呼び出す前にライター スレッドが呼び出された場合wait、リーダーは最後のエントリで無期限に待機する可能性があります。

また、このソリューションは複数のリーダー スレッドとライター スレッドに対して安全であることも付け加えておきます。複数のスレッドが同時に書き込みと取得を行うことLinkedBlockingQueueができ、 が並行性を処理します。

注意すべき唯一のことは、Objectが何らかの共有リソースにアクセスする場合ですが、これはオブジェクトのグループへの同時アクセスに関連する別の問題です。obj1(「2 つの異なるスレッドからobj2同時にアクセスできますか?」という行に沿って) これはまったく別の問題であるため、ここでは解決策について詳しく説明しません。

于 2012-09-18T15:06:04.200 に答える
1

すぐに何も起こらないことには何の価値もありません。スレッドに関しては、独立したイベントがいつ発生するかわかりません。(同期が必要な理由はどれですか)

final long start = System.nanoTime();
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.printf("Took %,d ns to start this thread%n", System.nanoTime() - start);
    }
}).start();

版画

Took 2,807,336 ns to start this thread

これは長い時間ではないように聞こえるかもしれませんが、3.2 GHz では、これはほぼ 900 万クロック サイクルです。その間にコンピュータは非常に多くのことを行うことができます。あなたの場合、2番目のスレッドが開始される前に、存続期間の短いスレッドが完了するまで実行できます。

2 番目のケースでは、ロックが公平ではないことがわかります (つまり、公平とは、最も長く待機しているものが最初にロックを取得することを意味します)。これは、これを適切に実装するのが非常に遅いためです (例: 10 倍以上遅い)。このため、ほとんどの場合、これがはるかに効率的であるため、最後にロックを保持しているスレッドにロックが与えられる傾向があります。を使用して公平なロックを取得できますがLock lock = new ReentrantLock(true);、ほとんどの場合、ほとんどの場合、速度が遅くなるため、必要な場合を除き、これは一般的に使用されません。

-XX:-UseBiasedLockingロックを少し公平にすることを試みることができます。


ExecutorService でほぼ同じことを行うには、次のようにコーディングできます

ExecutorService service = Executors.newSingleThreadExecutor();
// writer
for (int i = 0; i < 15; i++) {
    service.submit(new Runnable() {
        @Override
        public void run() {
            // reader
            System.out.println("remove first");
        }
    });
    System.out.println("sent");
}
service.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("wait");
    }
});
service.shutdown();

版画

sent
remove first
sent
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
sent
remove first
remove first
wait
于 2012-09-18T14:39:41.417 に答える
0

このようなシナリオで同期するためのより良い方法は、おそらく CountDownLatch の場合に java.util.concurrent.* を使用することです。

デッドロックの理由を探す前に、まずこれを試してみてください。

編集:ピーターは正しいです。順調そうですよね?

EDIT 2:OK、追加情報の後のまったく別の話。タイムアウトを使用して、特定の期間の後に書き込むものがさらにある場合でも、読み取りを少なくとも 1 回試行することをお勧めします。 waitタイムアウトのあるバージョンもあります... http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Object.html#wait(long )

繰り返しになりますが、個人的には同時実行 API を使用したいと思います。

于 2012-09-18T14:30:39.520 に答える