1

次のコード フラグメントに問題があります。これは、イベント キュー (ConcurrentLinkedQueue) に追加されるイベント (processEvent メソッドの呼び出しによって提供される) を処理することを目的としています。イベントはイベント キューに追加され、run メソッドで定期的に処理されます。

ほとんどの場合、すべて問題ありません。しかし、processEvent メソッドの呼び出し後、イベントがキューに追加されたときに、実行部分が新しいイベントの存在を認識できないことがあります。

何が間違っているかについて何か考えはありますか?String 定数をロックとして使用する明らかな間違い以外に?

import java.util.concurrent.ConcurrentLinkedQueue;

public class MyCommunicator implements Runnable {

private ConcurrentLinkedQueue<MyEvent> eventQueue = null;

private boolean stopped = false;

private String lock = "";
private Thread thread = null;

public MyCommunicator() {

    eventQueue = new ConcurrentLinkedQueue<MyEvent>();
}

public void start() {
    thread = new Thread(this, "MyCommunicatorThread");
    thread.start();
}

public void stop() {
    stopped = true;
    synchronized (lock) {
        lock.notifyAll();
    }
    eventQueue.clear();
}

public void run() {
    while (!stopped) {
        try {

            MyEvent event = null;
            while (!stopped && ((event = eventQueue.peek()) != null)) {
                sendEvent(event);
                eventQueue.poll();
            }

            if (!stopped) {
                synchronized (lock) {
                    lock.wait(10000L);
                }
            }
        }

        catch (Exception e) {

        }
    }
}

/**
 * START EVENT JOB - ADD A NEW EVENT TO BE PROCESSED
 */
public void processEvent(MyEvent event) {
    eventQueue.offer(event);
    synchronized (lock) {
        lock.notifyAll();
    }
}

/**
 * END EVENT JOB
 */
private void sendEvent(MyEvent event) {
    // do send event job
}

}
4

4 に答える 4

11

なぜロックと通知を使用しているのですか?

代わりにLinkedBlockingQueueを使用して、手間を省いてください。

タイムアウトを使用するpoll()と、実行しようとしているすべてのことが達成されます。


編集:現在のコードに関して;

「新しいイベントがあることを確認できない」を定義する必要があります。メソッドrun()は 10 秒ごとにキューを調べます。キューに何かがある場合、「それを見て」それを引き出します。

  • 「通知されてもすぐに表示されず、わずか 10 秒後に表示される」という意味であれば、それが発生しやすい競合状態があるため、答えるのはかなり簡単です。このスレッドがキューのチェック/処理を完了してからロックを取得するまでの間に、何かをキューに挿入できます。タイムアウトがwait()ないと、次のイベントが挿入されるまでブロックされます。stop()この間にメソッドが呼び出されていた場合、キュー内のすべてのイベントが失われます。不必要なロックと通知をすべて使用するのLinkedBlockingQueueではなく、 を使用すると、この問題が解決します。これは「簡単な」解決策ではありません。このユースケースと問題には正しい解決策です。

  • そうでない場合は、キューに何も挿入していないだけであり、ここに投稿していないコードに問題があります。MyEventそのコードについて何も知らずに推測すると、 に nullを挿入しようとしている可能性がありますeventQueue.offer(event)。あなたは試したりキャッチしたりしていないので、あなたはそれをoffer()知りません。すべての例外を無視し、戻り値をチェックしないことは、良い考えでも実践でもありません。

  • 3 番目の可能性は、このコードがハングする原因となる、まったく同じインターンされた文字列リテラル参照をロックしている他のコードがどこかにあることです。あなたはそれについて言及しましたが、ここで繰り返します-特にそれが空の文字列であることを考えると、それは本当に悪いことです. java.util.concurrentパッケージは、条件付きの実際のロックを提供しますここでそれらを使用することを主張する場合。これは、イベントを 10 秒間見逃すことがあるという競合状態を解消するものではありませんが、少なくともよりクリーンになることに注意してください。競合状態を解消するには、通常のキューの並行キューを捨てて、アクセスする前にロックを取得するだけです (挿入のロックを取得するだけでなく)。これにより、このスレッドがロック状態で待機していない限り、インサーターが挿入できないため、スレッドが同期されます。コードの同じチャンクでスレッド同期へのロックとロックフリーのアプローチを混在させると、多くの場合、これらの問題が発生します。

于 2011-04-15T20:44:48.857 に答える
5

あなたは逃した信号として知られているものを持っています。キューをポーリングしてから、モニターで待機します(ロックを取得します)。プロデューサースレッドはイベントを追加してから呼び出しますnotifyAll()(ロックを取得します)。happens-beforeイベントのキューイング/ポーリングと条件付きの待機/通知の間に関係はありません。

したがって、スレッドAが空の状態でポーリングしてからロックを取得しようとする可能性があります。その間、スレッドBは要素を追加してロックを取得し、待機中のすべてのスレッドに通知してからロックを解放します。次に、スレッドAがロックを取得して待機しますが、シグナルが失われています。

純粋に信号を送るためにロックを使用しているので、 DougLeaの新しいjdk7Phaserのような再利用可能なラッチなどの別のメカニズムを検討するか、BlockingQueue直接使用することができます。

または、単一のリーダースレッド用のBooleanLatchやマルチパーティサポート用のPhasedLatchなどのReusableLatchがいくつかあります。

于 2011-04-17T07:14:42.510 に答える
1

一見しただけでは特定のアイデアはありませんが、次の理由により、知らないうちに多くのことがうまくいかない可能性があります。

    catch (Exception e) {

    }

Exception(およびそのさまざまなサブクラスを含む)をキャッチしRuntimeException、それを無視するハンドラーは、一般的に悪い考えです。これが特定のタイプの例外をキャッチすることを意図している場合 (たとえば、InterruptedExceptionによってスローされる可能性が高いlock.wait()など)、その例外タイプに制限する必要があります。例外をキャッチする何らかの理由がある場合は、例外が発生したときに少なくとも何かをログに記録する必要があります。

于 2011-04-15T21:33:46.847 に答える
0

私が抱えていた問題はConcurrentLinkedQueue、実際には完全に同期されていないという点で、本物のバグであると本当に疑っています。

私はまだこれを完全にテストしていませんが、コードを見て、キューが実際に空の場合、.isEmpty() が同期されていないことを確信しています。1 つのスレッドが .isEmpty() を呼び出しtrueて返される間、キューには既に要素が含まれている可能性があります。

于 2012-04-05T09:11:44.747 に答える