0

(問題は解決しました。以下の解決策)
2 つのクラスがあります。Equip と Command です。装備はコマンドを実行する装備ですが、同時に1つのコマンドしか実行できないようにする必要があります。コマンドは run() 関数で実行されるスレッドですが、Equip は何も拡張しない通常のクラスです。現在、コマンドを実行するための次のセットアップがあります。

コマンドクラス:

@Override
public void run() {
    boolean execute = equip.queueCommand(this);
    if (!execute) {
        // if this command is the only one on the queue, execute it, or wait.
        esperar();
    }
    // executes the command.....
    equip.executeNextCommand();
}


synchronized public void esperar() {
    try {
        this.wait();
    } catch (Exception ex) {
        Log.logErro(ex);
    }
}

synchronized public void continue() {
    this.notifyAll();
}

装備クラス:

public boolean queueCommand(Command cmd) {
    // commandQueue is a LinkedList
    commandQueue.addLast(cmd);
    return (commandQueue.size() == 1);
}

public void executeNextCommand() {
    if (commandQueue.size() >= 1) {
        Command cmd = commandQueue.pollFirst();
        cmd.continue();
    }
}

ただし、これは機能していません。基本的に、notify() はコマンド スレッドを起動しないため、決して実行されません。待機および通知プロトコルについて検索しましたが、コードに問題は見つかりませんでした。また、queueCommand() メソッドから直接 wait() を呼び出してみましたが、queueCommand の実行が停止し、本来の動作も実行されませんでした。このアプローチは正しいですか、何かが欠けていますか、それとも完全に間違っているので、並行スレッドを操作するために Monitor クラスを実装する必要がありますか?

編集:@Grayのおかげで、エグゼキューターを使用して、別のまったく異なるアプローチを使用して問題を解決しました。

これが最終的なコードです。いつか誰かを助けるかもしれません:

装備クラス:

private ExecutorCompletionService commandQueue = new ExecutorCompletionService(Executors.newFixedThreadPool(1));

public void executeCommand(Command cmd, boolean waitCompletion) {
    commandQueue.submit(cmd, null);
    if (waitCompletion) {
        try {
            commandQueue.take();
        } catch (Exception ex) {
        }
    }
}

Command クラスには、装備の execute メソッドをカプセル化するメソッドがあります。ブール型の waitCompletion は、コマンドの結果が同時に必要な場合に使用されます。新しいスレッドを呼び出して実行する代わりに、同じスレッドで実行されているふりをして、実行して待機するだけです。この質問には、この問題に関する適切な議論が含まれています。. はい、これは .start() の代わりに .run() を呼び出すと便利な場合です。

4

3 に答える 3

2

が複数のスレッドから呼び出された場合、コードに多数の競合状態が存在します。Command.run()コードを自分で実装しなければならない宿題のような問題でない限りExecutors、1.6 で追加された Java のいずれかを使用することを強くお勧めします。この場合、Executors.newSingleThreadExecutor()実行中のバックグラウンド タスクの数を 1 に制限する必要があります。これにより、無制限の数のタスクを に送信できますが、ExecutorService一度に実行されるタスクは 1 つだけです。

別のタスクが既に実行されているときにブロックするタスクを送信しているスレッドが必要な場合は、次のようなものを使用します。これは、最大 1 つのスレッドのプールをセットアップしSynchronousQueue、ワーカー スレッドがジョブを消費するまでブロックを使用します。

final ExecutorService executorServer =
    new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
         new SynchronousQueue<Runnable>());

しかし、その場合は、タスクをsynchronizedブロック内で直接呼び出すだけで、ExecutorService.

最後に、(言語を問わず) 並行処理を始めたばかりのプログラマーには、時間を取ってこの件に関するドキュメントを読むことをお勧めします。最も単純なクラスのセットであっても、スレッド化に固有の同時実行の落とし穴を認識し始めるまでは、コードを機能させるのはイライラするプロセスになるでしょう。 Doug Lea の本は、このテーマに関するバイブルの 1 つです。この分野での経験を過小評価していた場合は、お詫び申し上げます。

于 2012-03-08T20:03:39.090 に答える
0

ExecutorService がその方法です。しかし、自分でやりたい場合、またはもっと手の込んだことをする必要がある場合は、次のことをお勧めします。

このすべてが、いつでもどこでも任意のスレッドから呼び出される可能性がある Equip の queueCommand によって駆動されると私は推測します。まず、Equip の 2 つのメソッドを同期して、commandQueue が破棄されないようにする必要があります。(ConcurrentLinkedQueue を使用することもできますが、カウントには注意してください。)さらに良いのは、各メソッドのコードを、queueCommand によって同期されるブロックに配置することです。

しかしさらに、あなたの 2 つのクラスを組み合わせるとより効果的だと思います。Command を単純な Runnable に切り替えると、次のようになります。

class Equip  {
    private Object  queueLock = new Object();  // Better than "this". 
    private LinkedList<Runnable>  commandQueue = new LinkedList<Runnable>();

    private void run() {
        for (;;)  {
            Runnable  cmd = equip.getNextCommand();
        if (cmd == null)  {
                // Nothing to do.
                synchronized (queueLock)  { queueLock.wait(); }
            }
            else
                cmd.run();
        }
    }
    // Adds commands to run.
    public boolean queueCommand( Runnable cmd )  {
        synchronized (queueCommand)  { commandQueue.addLast( cmd ); }
        synchronized (queueLock)  {
            // Lets "run" know queue has something in it if it
            // is in a wait state.
            queueLock.notifyAll();
        }
    }
    private Runnable getNextCommand()  {
        synchronized (queueCommand)  { return commandQueue.pollFirst(); }
    }
}

いくつかの例外をキャッチし、物事を開始してシャットダウンする方法を理解する必要がありますが、これにより、待機と通知がどのように機能するかがわかります。(「実行」がいつ待機していないかを知る方法を探しているので、queueCommand の queueLock での同期をスキップできますが、実行する前に歩きます。)

于 2012-03-08T21:43:23.677 に答える
0

esperarメソッドで「同期」するべきではなかったと思います。これにより、オブジェクト インスタンスをロック オブジェクトとして使用することがブロックされます。待機を試みる他のスレッドは、待機中ではなく、AT ENTRY TO THE METHOD をブロックします。したがって、notifyAll は、最初にメソッドに入った 1 つのスレッドを解放します。残りの呼び出し元のうち、1 つだけが esperar への呼び出しに進み、それが wait() でブロックされます。すすいで繰り返します。

于 2012-03-08T20:00:54.280 に答える