web-dev-qa-db-ja.com

ブロッキングキューを「閉じる」

非常に単純な生産者/消費者シナリオでJava.util.concurrent.BlockingQueueを使用しています。例えば。この擬似コードは、コンシューマー部分を表しています。

class QueueConsumer implements Runnable {

    @Override
    public void run() {
        while(true)
        {
            try {
                ComplexObject complexObject = myBlockingQueue.take();
                //do something with the complex object
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

ここまでは順調ですね。ブロッキングキューのjavadocで、次のように読みました。

BlockingQueueは、アイテムが追加されないことを示すための「閉じる」または「シャットダウン」操作を本質的にサポートしていません。このような機能のニーズと使用法は、実装に依存する傾向があります。たとえば、一般的な戦術は、プロデューサーが特別なエンドオブストリームまたはポイズンオブジェクトを挿入することです。これらは、コンシューマーが取得したときにそれに応じて解釈されます。

残念ながら、使用されているジェネリックとComplexObjectの性質により、「ポイズンオブジェクト」をキューにプッシュするのは簡単ではありません。したがって、この「一般的な戦術」は、私のシナリオではあまり便利ではありません。

私の質問は、キューを「閉じる」ために他にどのような優れた戦術/パターンを使用できるかということです。

ありがとうございました!

27
Lachezar Balev

コンシューマースレッドへのハンドルがある場合は、それを中断できます。あなたが与えたコードで、それは消費者を殺します。私はプロデューサーがこれを持っているとは思いません。おそらく、プログラムコントローラーにコールバックして、完了したことを通知する必要があります。次に、コントローラーはコンシューマースレッドを中断します。

割り込みに従う前に、いつでも作業を終了できます。例えば:

_class QueueConsumer implements Runnable {
    @Override
    public void run() {
        while(!(Thread.currentThread().isInterrupted())) {
            try {
                final ComplexObject complexObject = myBlockingQueue.take();
                this.process(complexObject);

            } catch (InterruptedException e) {
                // Set interrupted flag.
                Thread.currentThread().interrupt();
            }
        }

        // Thread is getting ready to die, but first,
        // drain remaining elements on the queue and process them.
        final LinkedList<ComplexObject> remainingObjects;
        myBlockingQueue.drainTo(remainingObjects);
        for(ComplexObject complexObject : remainingObjects) {
            this.process(complexObject);
        }
    }

    private void process(final ComplexObject complexObject) {
        // Do something with the complex object.
    }
}
_

とにかくキューを中毒するよりも、実際にはそれを好むでしょう。スレッドを強制終了する場合は、スレッドにそれ自体を強制終了するように依頼してください。

(誰かがInterruptedExceptionを適切に処理しているのを見るのは素晴らしいことです。)


ここでは、中断の処理についていくつかの論争があるようです。まず、皆さんにこの記事を読んでいただきたいと思います: http://www.ibm.com/developerworks/Java/library/j-jtp05236.html

さて、誰も実際にそれを読んでいないことを理解した上で、これが取引です。スレッドは、割り込み時に現在ブロックされていた場合にのみInterruptedExceptionを受け取ります。この場合、Thread.interrupted()falseを返します。ブロックしていなかった場合、この例外は受け取られず、代わりにThread.interrupted()trueを返します。したがって、ループガードは、何があっても、絶対にThread.interrupted()をチェックする必要があります。そうしないと、スレッドの中断を見逃すリスクがあります。

つまり、何があってもThread.interrupted()をチェックしていて、InterruptedExceptionをキャッチする必要があるため(強制されていなくても処理する必要があります)、2つあります。同じイベント、スレッドの中断を処理するコード領域。これを処理する1つの方法は、それらを1つの条件に正規化することです。つまり、ブール状態チェックで例外をスローするか、例外でブール状態を設定できます。後で選びます。


編集:静的Thread#interruptedメソッドは、現在のスレッドの割り込みステータスをクリアすることに注意してください。

19
jdmichal

これを簡単にするための別のアイデア:

_class ComplexObject implements QueueableComplexObject
{
    /* the meat of your complex object is here as before, just need to
     * add the following line and the "implements" clause above
     */
    @Override public ComplexObject asComplexObject() { return this; }
}

enum NullComplexObject implements QueueableComplexObject
{
    INSTANCE;

    @Override public ComplexObject asComplexObject() { return null; }
}

interface QueueableComplexObject
{
    public ComplexObject asComplexObject();
}
_

次に、_BlockingQueue<QueueableComplexObject>_をキューとして使用します。キューの処理を終了したい場合は、queue.offer(NullComplexObject.INSTANCE)を実行してください。消費者側では、

_boolean ok = true;
while (ok)
{
    ComplexObject obj = queue.take().asComplexObject();
    if (obj == null)
        ok = false;
    else
        process(obj);
}

/* interrupt handling elided: implement this as you see fit,
 * depending on whether you watch to swallow interrupts or propagate them
 * as in your original post
 */
_

instanceofは不要であり、偽のComplexObjectを作成する必要はありません。これは、実装によっては費用がかかる/難しい場合があります。

11
Jason S

別の方法は、実行中の処理を ExecutorService でラップし、ジョブをキューに追加するかどうかをExecutorService自体に制御させることです。

基本的に、 ExecutorService.shutdown() を利用します。これを呼び出すと、エグゼキュータによるタスクの処理が禁止されます。

例のQueueConsumerに現在どのようにタスクを送信しているかわかりません。ある種のsubmit()メソッドがあると仮定し、例で同様のメソッドを使用しました。

import Java.util.concurrent.*;

class QueueConsumer {
    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    public void shutdown() {
        executor.shutdown(); // gracefully shuts down the executor
    }

    // 'Result' is a class you'll have to write yourself, if you want.
    // If you don't need to provide a result, you can just Runnable
    // instead of Callable.
    public Future<Result> submit(final ComplexObject complexObject) {
        if(executor.isShutdown()) {
            // handle submitted tasks after the executor has been told to shutdown
        }

        return executor.submit(new Callable<Result>() {
            @Override
            public Result call() {
                return process(complexObject);
            }
        });
    }

    private Result process(final ComplexObject complexObject) {
        // Do something with the complex object.
    }
}

この例は、Java.util.concurrentパッケージが提供するものの単なる説明です。おそらく、いくつかの最適化を行うことができます(たとえば、独自のクラスはおそらく必要ないため、QueueConsumer。タスクを送信するプロデューサーにExecutorServiceを提供するだけで済みます)。 。

Java.util.concurrentパッケージを掘り下げます(上記のリンクのいくつかから開始します)。あなたはそれがあなたがやろうとしていることのためにあなたにたくさんの素晴らしいオプションを与えることに気付くかもしれません、そしてあなたは仕事の列を調整することについて心配する必要さえありません。

9
Rob Hruska

毒オブジェクトを作成する別の可能性:それをクラスの特定のインスタンスにします。このように、サブタイプをいじったり、ジェネリックを台無しにする必要はありません。

欠点:プロデューサーとコンシューマーの間に何らかのシリアル化の障壁がある場合、これは機能しません。

public class ComplexObject
{
    public static final POISON_INSTANCE = new ComplexObject();

    public ComplexObject(whatever arguments) {
    }

    // Empty constructor for creating poison instance.
    private ComplexObject() {
    }
}

class QueueConsumer implements Runnable {
    @Override
    public void run() {
        while(!(Thread.currentThread().interrupted())) {
            try {
                final ComplexObject complexObject = myBlockingQueue.take();
                if (complexObject == ComplexObject.POISON_INSTANCE)
                    return;

                // Process complex object.

            } catch (InterruptedException e) {
                // Set interrupted flag.
                Thread.currentThread().interrupt();
            }
        }
    }
}
6
jdmichal

汎用オブジェクトをデータオブジェクトにラップできます。このデータオブジェクトには、ポイズンオブジェクトのステータスなどのデータを追加できます。データオブジェクトは、2つのフィールドを持つクラスです。 T complexObject;およびboolean poison;

コンシューマーは、キューからデータオブジェクトを取得します。ポイズンオブジェクトが返された場合は、コンシューマーを閉じます。それ以外の場合は、ジェネリックをアンラップして「process(complexObject)」を呼び出します。

Java.util.concurrent.LinkedBlockingDeque<E>を使用しているので、キューの最後にオブジェクトを追加して、それらを先頭から取得できます。そうすれば、オブジェクトは順番に処理されますが、より重要なのは、poisonオブジェクトに遭遇した後にキューを閉じた方が安全です。

複数のコンシューマーをサポートするために、poisonオブジェクトに遭遇したときにキューに戻します。

public final class Data<T> {
    private boolean poison = false;
    private T complexObject;

    public Data() {
        this.poison = true;
    }

    public Data(T complexObject) {
        this.complexObject = complexObject;
    }

    public boolean isPoison() {
        return poison;
    }

    public T getComplexObject() {
        return complexObject;
    }
}
public class Consumer <T> implements Runnable {

    @Override
    public final void run() {
        Data<T> data;
        try {
            while (!(data = queue.takeFirst()).isPoison()) {
                process(data.getComplexObject());
            }
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            return;
        }
        // add the poison object back so other consumers can stop too.
        queue.addLast(line);
    }
}
3
Dorus

ComplexObjectを拡張して、重要な作成機能をモックアウトすることは可能ですか?基本的にはShellオブジェクトになりますが、instance ofを実行して、キューオブジェクトの終わりかどうかを確認できます。

3
Chris Knight

クローズ可能なBlockingQueueを実装することは私には合理的だと思われます。

_import Java.util.concurrent.BlockingQueue;

public interface CloseableBlockingQueue<E> extends BlockingQueue<E> {
    /** Returns <tt>true</tt> if this queue is closed, <tt>false</tt> otherwise. */
    public boolean isClosed();

    /** Closes this queue; elements cannot be added to a closed queue. **/
    public void close();
}
_

次の動作でこれを実装するのは非常に簡単です( メソッドの要約テーブル を参照)。

  • 挿入

  • 削除

  • 調べる

    変化なし。

source を編集してこれを行いました。 github.com で見つけてください。

1
Corin

この状況では、通常、ジェネリックを破棄して、キューホールドタイプをオブジェクトにする必要があります。次に、実際のタイプにキャストする前に、「毒」オブジェクトをチェックする必要があります。

0
jtahlborn

今日、私はラッパーオブジェクトを使用してこの問題を解決しました。 ComplexObjectは複雑すぎてサブクラス化できないため、ComplexObjectをComplexObjectWrapperオブジェクトにラップしました。次に、ジェネリック型としてComplexObjectWrapperを使用しました。

public class ComplexObjectWrapper {
ComplexObject obj;
}

public class EndOfQueue extends ComplexObjectWrapper{}

BlockingQueue<ComplexObject>の代わりにBlockingQueue<ComplexObjectWrapper>を実行しました

私はコンシューマーとプロデューサーの両方を制御していたので、このソリューションは私のために機能しました。

0
ArnabRaxit

私はこのシステムを使用しました:

ConsumerClass
private boolean queueIsNotEmpty = true;//with setter
...
do {
    ...
    sharedQueue.drainTo(docs);
    ...
} while (queueIsNotEmpty || sharedQueue.isEmpty());

プロデューサーが終了したら、consumerObject、queueIsNotEmptyフィールドをfalseに設定しました

0
daliborn