1

このページの例に基づいてスレッドプールを作成しました。ワーカースレッドには、スレッドを停止させない無限ループと、実行する作業がないときにスレッドを一時停止するwait()メソッド呼び出しがあります。

while (true) {
    synchronized(queue) {
         loop:while (queue.isEmpty()) { // labled the loop so we can return here
             try
             {
                 queue.wait();
                 if(queue.isEmpty())    // check the condition predicate again
                     continue loop;
             }
             catch (InterruptedException ignored)
             {
             }
         }    
         r = (Runnable) queue.removeFirst();
 }

// If we don't catch RuntimeException, 
// the pool could leak threads
try {
    r.run();
}
catch (RuntimeException e) {
    // You might want to log something here
}

実際には、キューが空の場合、RuntimeExceptionであるr = (Runnable) queue.removeFirst();をスローする可能性があります。NoSuchElementExceptionそして、そのような例外がその行でスローされると、ミューテックスを保持している現在のスレッドが停止し、プールがスレッドをリークします。スレッドが終了すると、ミューテックスが解放されるようです。

ただし、デフォルトのsynchronizedキーワードを使用してキューを同期する代わりに、を使用しReentrantLockてロックしCondition、シグナリングと待機を行う場合、ミューテックスを保持している現在のスレッドは、予期せず中断したときにロックを解放しないようです。

したがって、私の場合、[スレッド]タブでJVisualVMを確認すると、AWT-EventQueue-0スレッドがThread-1ミューテックスの解放を待っていることがわかりました。しかし、Thread-1はタスクを実行する途中で停止し、予期せず終了し(RuntumeException)、ミューテックスは解放されなかったようです。

私の質問:

1) ReentrantLocksを保持しているスレッドが予期せず終了した場合、ReentrantLocksは解放されませんか?

2)上記のコードスニペットwhile (queue.isEmpty()) {との間に違いはありますか?if (queue.isEmpty()) {どちらの場合もスレッドは待機するため、違いはわかりません。ただし、使用時の動作は異なると思いますif(複数のスレッドがキューに影響を与える可能性がある場合など)。


実際のJava同時実行の編集状態:

これらすべての理由から、待機から復帰するときは、条件述語を再度テストし、それがまだ真でない場合は待機に戻る(または失敗する)必要があります。条件述語がtrueでなくても繰り返しウェイクアップできるため、常にループ内からwaitを呼び出して、各反復で条件述語をテストする必要があります。

上記のコードでの私の編集を見てください。これで、コードはJava ConcurrencyinPracticeに記載されているとおりに正しくなるはずです。

4

3 に答える 3

4

1) ReentrantLocks を保持しているスレッドが正常に終了した場合、ReentrantLocks は解放されませんか?

Lock #unlock()を明示的に呼び出した場合にのみロック解除します。そのため、アプリケーションのデッドロックを防ぐために、finally ブロックでLock#unlock()を呼び出すことをお勧めします。

2) 上記のコード スニペットの while (queue.isEmpty()) { と if (queue.isEmpty()) { に違いはありますか? スレッドはどちらの場合も待機するため、違いはわかりません。しかし、if を使用すると、動作が異なると思います (複数のスレッドがキューに影響を与える可能性がある場合など)。

特に状況に大きな違いはありません。ただしwhile、アプリケーションで保証アサートを使用すると、removeFirst()キューが空のときに呼び出されません。また、 if の代わりに while を使用する場合の長所は、偽の wakeupsです。


注: このスキーマを教育目的以外で実装する場合は、BlockingQueueの使用を検討してください。java.util.concurrent ライブラリは多くのマルチスレッドの問題を解決し、ほとんどの場合、wait()/notify() などの低レベルの手法を使用する代わりに、java.util.concurrent の高レベルの抽象化に基づいてアプリケーションを構築できます。

于 2013-02-08T06:37:43.787 に答える
1

あなたのコードは複雑すぎるようです - 私は単純に次のように書きます:

while (true) {
    synchronized(queue) {
         while (queue.isEmpty()) {
             try {
                 queue.wait();
             } catch (InterruptedException ignored) {
                 //don't ignore me please
                 //you probably should exit the loop and return here...
             }
         }
         r = queue.removeFirst(); //Why use a cast? Use generics instead.
     }
 }

NoSuchElementException をスローする唯一の状況はqueue.removeFirst()、同時に変更された場合です。これは、キューへのすべてのアクセスが同期ブロックで行われる場合には不可能です。

したがって、モニターのロックを保持せずにキューにアクセスする場所を見つければ、問題を解決できます。

ループ内で呼び出さなければならない理由waitは、wait が誤ってウェイクアップする可能性があるためです (つまり、wait がウェイクアップしても、条件が true になったことを意味しないため、再度テストする必要があるためです)。


補足として、BlockingQueueを使用した場合、これらの低レベルの詳細について心配する必要はなく、単純に次のように記述できます。

while(true) {
    Runnable r = queue.take(); //blocks until queue is not empty
}
于 2013-02-10T09:53:25.603 に答える
0

時間指定待機や try-lock/acquire メソッドなどにより、柔軟性が向上しjava.util.concurrentます。また、lock.lock()/unlock() のペアとは異なり、synchronized はコード ブロックを提供します。また、競合がない場合は、より効率的になる傾向があります。java.util.concurrentとにかく、並行性を使用する場合は、多くの問題がすでに解決されているため、必ず検討する必要があります。

于 2013-02-08T14:26:45.233 に答える