web-dev-qa-db-ja.com

Javaでスレッドを高速かつクリーンな方法で中止する方法は?

これが私の問題です。ユーザーが(たとえば、スピナーを介して)変更できるいくつかのパラメーターを含むダイアログがあります。これらのパラメーターのいずれかが変更されるたびに、新しいパラメーター値に従って3Dビューを更新するスレッドを起動します。最初のスレッドが機能しているときにユーザーが別の値(またはスピナーの矢印を何度もクリックして同じ値)を変更した場合、最初のスレッド(および3Dビューの更新)を中止して新しいスレッドを起動します最新のパラメータ値。

どうすればそのようなことができますか?

PS:スレッドのrun()メソッドにはループがないため、フラグのチェックはオプションではありません。3Dビューを更新するスレッドは、基本的に、実行に非常に長い単一のメソッドのみを呼び出します。このメソッドには、コードにアクセスできないため、中止を要求するフラグを追加できません。

28
jumar

スレッドに何らかの影響があるかどうかを確認するために、interrupt()を試してみてください。そうでない場合は、スレッドを停止させるリソースを破棄またはクローズしてみてください。これは、Thread.stop()をスローするよりも少し良い可能性があります。

パフォーマンスが許容できる場合は、各3D更新を個別の非中断イベントとして表示し、それを最後まで実行して、後で実行する新しい最新の更新があるかどうかを確認します。ユーザーは5つの変更を行うことができ、5つの変更前の状態からグラフィカルな結果を確認し、最新の変更の結果を確認できるため、ユーザーにとってGUIが少し不安定になる可能性があります。ただし、このプロセスの長さによっては許容できる場合があり、スレッドを強制終了する必要がなくなります。デザインは次のようになります。

boolean stopFlag = false;
Object[] latestArgs = null;

public void run() {
  while (!stopFlag) {
    if (latestArgs != null) {
      Object[] args = latestArgs;
      latestArgs = null;
      perform3dUpdate(args);
    } else {
      Thread.sleep(500);
    }
  }
}

public void endThread() {
  stopFlag = true;
}

public void updateSettings(Object[] args) {
  latestArgs = args;
}
12
skiphoppy

3Dビューを更新しているスレッドは、いくつかのフラグを定期的にチェックして(_volatile boolean_を使用)、終了する必要があるかどうかを確認する必要があります。スレッドを中止したい場合は、フラグを設定するだけです。スレッドが次にフラグをチェックするとき、ビューを更新してrunメソッドから戻るために使用しているループから抜け出すだけです。

スレッドが実行しているコードに本当にアクセスしてフラグをチェックすることができない場合、スレッドを停止する安全な方法はありません。このスレッドは、アプリケーションが完了する前に正常に終了しますか?その場合、何が原因で停止しますか?

長期間実行し、単に終了する必要がある場合は、廃止されたThread.stop()メソッドの使用を検討できます。しかし、それは正当な理由で廃止されました。なんらかの操作の途中で不整合な状態になっているスレッドや、一部のリソースが適切にクリーンアップされていないときにそのスレッドが停止すると、問題が発生する可能性があります。これが documentation からのメモです:

この方法は本質的に安全ではありません。 Thread.stopでスレッドを停止すると、それがロックしているすべてのモニターのロックが解除されます(チェックされていないThreadDeath例外がスタックを伝播する自然な結果として)。これらのモニターによって以前に保護されていたオブジェクトのいずれかが不整合な状態であった場合、損傷したオブジェクトは他のスレッドから見えるようになり、潜在的に任意の動作が発生します。 stopの多くの使用法は、ターゲットスレッドが実行を停止する必要があることを示すために、いくつかの変数を単に変更するコードに置き換える必要があります。ターゲットスレッドはこの変数を定期的にチェックし、変数が実行を停止することを示している場合は、そのrunメソッドから整然とした方法で戻る必要があります。ターゲットスレッドが長時間(条件変数などで)待機する場合は、割り込みメソッドを使用して待機を中断する必要があります。詳細については、 Thread.stop、Thread.suspend、Thread.resumeが推奨されない理由 を参照してください。

8
Dave L.

独自のブールフラグをロールする代わりに、Javaスレッドですでにスレッド割り込みメカニズムを使用していないのはなぜですか?変更できないコードで内部がどのように実装されているかによって、変更できる場合があります。実行の一部も中止します。

外の糸:

if(oldThread.isRunning())
{
    oldThread.interrupt();
    // Be careful if you're doing this in response to a user
    // action on the Event Thread
    // Blocking the Event Dispatch Thread in Java is BAD BAD BAD
    oldThread.join();
}

oldThread = new Thread(someRunnable);
oldThread.start();

インナーランナブル/スレッド:

public void run()
{
    // If this is all you're doing, interrupts and boolean flags may not work
    callExternalMethod(args);
}

public void run()
{
    while(!Thread.currentThread().isInterrupted)
    {
        // If you have multiple steps in here, check interrupted peridically and
        // abort the while loop cleanly
    }
}
4
basszero

これは、「Thread.stop()以外のメソッドが使用できない場合にスレッドを中止するにはどうすればよいですか」と尋ねるようなものではありませんか?

明らかに、唯一の有効な答えはThread.stop()です。その醜い、いくつかの状況で物事を壊す可能性があり、メモリ/リソースリークにつながる可能性があり、TLEJD(League of Extraordinary Java Developers))に眉をひそめられますが、それでも、このようなケースはほとんどありません。サードパーティのコードで利用できるcloseメソッドがない場合、他に方法はありません。

OTOH、バックドアを閉じる方法が時々あります。つまり、動作している基になるストリーム、またはその動作に必要なその他のリソースを閉じます。ただし、これは、単にThread.stop()を呼び出してThreadDeathExceptionを発生させるよりは、ましです。

3
jsight

この質問に対する承認された回答では、バッチ作業をバックグラウンドスレッドに送信できます。これはそのためのより良いパターンかもしれません:

public abstract class dispatcher<T> extends Thread {

  protected abstract void processItem(T work);

  private List<T> workItems = new ArrayList<T>();
  private boolean stopping = false;
  public void submit(T work) {
    synchronized(workItems) {
      workItems.add(work);
      workItems.notify();
    }
  }
  public void exit() {
    stopping = true;
    synchronized(workItems) {
      workItems.notifyAll();
    }
    this.join();
  }
  public void run() {
    while(!stopping) {
      T work;
      synchronized(workItems) {
        if (workItems.empty()) {
            workItems.wait();
            continue;
        }
        work = workItems.remove(0);
      }
      this.processItem(work);
    }
  }
}

このクラスを使用するには、このクラスを拡張して、Tの型とprocessItem()の実装を提供します。次に、1つを作成し、その上でstart()を呼び出します。

あなたはabortPendingメソッドを追加することを検討するかもしれません:

public void abortPending() {
  synchronized(workItems) {
    workItems.clear();
  }
}

ユーザーがレンダリングエンジンの前にスキップし、これまでにスケジュールされた作業を破棄したい場合。

2
nsayer

ブールフィールドの使用を目的とするソリューションは正しい方向です。しかし、フィールドは揮発性でなければなりません。 Java言語仕様 says

「たとえば、次の(壊れた)コードフラグメントでは、this.doneが不揮発性のブールフィールドであると想定します。

while(!this.done)
 Thread.sleep(1000); 

コンパイラーはフィールドthis.doneを1回だけ自由に読み取り、ループの各実行でキャッシュされた値を再利用します。これは、別のスレッドがthis.doneの値を変更した場合でも、ループが終了しないことを意味します。」

私が覚えている限り、 "Java Concurrency in Pratice" Java.lang.Threadの interrupt() および interrupted() メソッドを使用する目的。

1
dmeister

過去にこのようなものを実装した方法は、Runnableサブクラスにshutdown()メソッドを実装して、_should_shutdown_というインスタンス変数をtrueに設定することです。 run()メソッドは通常ループ内で何かを実行し、_should_shutdown_を定期的にチェックし、trueの場合はdo_shutdown()を返すか、呼び出してから戻ります。

現在のワーカースレッドへの参照を手元に置いておく必要があります。ユーザーが値を変更したら、現在のスレッドでshutdown()を呼び出し、シャットダウンするまで待ちます。その後、新しいスレッドを起動できます。

_Thread.stop_を使用することはお勧めしません。前回チェックしたときに廃止されたためです。

編集:

ワーカースレッドが実行に時間がかかる別のメソッドを呼び出す方法についてのコメントを読んでください。そのため、上記は当てはまりません。この場合、本当の選択肢はinterrupt()を呼び出して、効果があるかどうかを確認することです。そうでない場合は、ワーカースレッドが呼び出している関数を手動で中断させることを検討してください。たとえば、複雑なレンダリングを行っているように聞こえるので、キャンバスを破壊して例外をスローする可能性があります。これは良い解決策ではありませんが、私の知る限り、これはこのようなスーツでスレッドを停止する唯一の方法です。

1
freespace

run()メソッドが完了すると、スレッドは終了します。そのため、メソッドを完了するためのチェックが必要です。

スレッドを中断してから、定期的にisInterrupted()をチェックしてrun()メソッドから戻るチェックを行うことができます。

また、スレッド内で定期的にチェックされるブール値を使用して、そうであればそれを返すようにしたり、繰り返しタスクを実行している場合はループ内にスレッドを置いたり、ブール値を設定したときにrun()メソッドを終了したりすることもできます。例えば、

static boolean shouldExit = false;
Thread t = new Thread(new Runnable() {
    public void run() {
        while (!shouldExit) {
            // do stuff
        }
    }
}).start();
1
Rich Adams

画面をレンダリングしているスレッドを制御していないように見えますが、スピナーコンポーネントを制御しているように見えます。スレッドが画面をレンダリングしている間、スピナーを無効にします。このようにして、ユーザーは少なくとも自分の行動に関連するいくつかのフィードバックを持っています。

1
Javamann

ユーザーが値を何度も変更した場合にスレッドが1回だけ実行されるように、待機と通知を使用して複数のスレッドを防止することをお勧めします。ユーザーが値を10回変更すると、最初の変更時にスレッドが起動され、スレッドが完了する前に行われた変更はすべて1つの通知に「ロールアップ」されます。それはスレッドを停止しませんが、あなたの説明に基づいてそれを行う良い方法はありません。

残念ながら、ロックによって同期できるリソースを使用する可能性があるため、スレッドの強制終了は本質的に安全ではなく、現在強制終了しているスレッドにロックがあると、プログラムがデッドロックになる可能性があります(取得できないリソースを常に取得しようとする試み)。 。停止するスレッドから強制終了する必要があるかどうかを手動で確認する必要があります。揮発性は、以前に保存されたものではなく、変数の真の値をチェックすることを保証します。余談ですが、常にチェックするのではなく、終了するスレッドが実際になくなるまで待機してから、何かを行う前に、終了するスレッドのThread.joinに注意してください。

1
bmeck

コードを扱っているので、アクセスできない場合は、おそらく運が悪いでしょう。標準的な手順(他の回答で概説されている)は、実行中のスレッドによって定期的にチェックされるフラグを持つことです。フラグが設定されている場合は、クリーンアップして終了します。

このオプションは使用できないため、実行中のプロセスを強制終了する以外にオプションはありません。これは、以前は Thread.stop() を呼び出すことで可能でしたが、そのメソッドは次の理由により永久に非推奨になりました(javadocsからコピー)。

この方法は本質的に安全ではありません。 Thread.stopでスレッドを停止すると、それがロックしているすべてのモニターのロックが解除されます(チェックされていないThreadDeath例外がスタックを伝播する自然な結果として)。これらのモニターによって以前に保護されていたオブジェクトのいずれかが不整合な状態であった場合、損傷したオブジェクトは他のスレッドから見えるようになり、潜在的に任意の動作が発生します。

このトピックの詳細については、 ここ を参照してください。

要求を確実に達成できる確実な方法の1つ(これは非常に効率的な方法ではありません)は、新しいJavaプロセス Runtime.exec() を使用して開始することです=そして、必要に応じて Process.destroy() を介してそのプロセスを停止します。ただし、このようなプロセス間で状態を共有することは、簡単なことではありません。

0
toluju

正解は、スレッドを使用しないことです。

エグゼキューターを使用する必要があります。パッケージを参照してください:Java.util.concurrent

0
Pyrolistical

多分これはあなたを助けるかもしれません: Javaで実行中のスレッドをどのように殺すことができますか?

外部クラス変数を設定することにより、特定のスレッドを強制終了できます。

_ Class Outer
 {    
    public static flag=true;
    Outer()
    {
        new Test().start();
    } 
    class Test extends Thread
    {               
       public void run()
       {
         while(Outer.flag)
         {
          //do your work here
         }  
       }
    }
  } 
_

上記のスレッドを停止する場合は、フラグ変数をfalseに設定します。スレッドを強制終了するもう1つの方法は、スレッドをThreadGroupに登録してから、destroy()を呼び出すだけです。この方法は、グループとして作成したり、グループに登録したりすることで、同様のスレッドを強制終了するためにも使用できます。

0
Olvagor

スレッドの開始と停止を操作する代わりに、インターフェイスで変更するプロパティをスレッドに監視させることを検討しましたか?ある時点でスレッドの停止条件が必要になることもありますが、これも可能でした。あなたがMVCのファンなら、これはその種のデザインにうまく適合します

申し訳ありませんが、質問をもう一度読んでも、これも他の「変数のチェック」の提案も問題を解決しません。

0
Nerdfest