Javaでスレッドを適切に停止する方法は?


276

Javaでスレッドを適切に停止するソリューションが必要です。

私が持っているIndexProcessor、Runnableインタフェースを実装するクラスを:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    @Override
    public void run() {
        boolean run = true;
        while (run) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                run = false;
            }
        }

    }
}

そして、私はServletContextListenerスレッドを開始および停止するクラスを持っています:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        thread = new Thread(new IndexProcessor());
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            thread.interrupt();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

しかし、Tomcatをシャットダウンすると、IndexProcessorクラスで例外が発生します。

2012-06-09 17:04:50,671 [Thread-3] ERROR  IndexProcessor Exception
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at lt.ccl.searchengine.processor.IndexProcessor.run(IndexProcessor.java:22)
    at java.lang.Thread.run(Unknown Source)

JDK 1.6を使用しています。だから問題は:

スレッドを停止して例外をスローしないようにするにはどうすればよいですか?

PS.stop();メソッドは非推奨であるため、使用したくありません。


1
スレッドの途中で終了すると、常に例外が発生します。それが正常な動作である場合は、単にをキャッチして無視できますInterruptedException。これは私の考えですが、標準的な方法はどのようになっているのかも不思議です。
nhahtdh

私はスレッドをあまり使用していないので、スレッドに慣れていないので、例外を無視するのが通常の動作かどうかわかりません。それが私が求めている理由です。
Paulius Matulionis

多くの場合、例外を無視してメソッド処理を終了するのは正常な動作です。これがフラグベースのアプローチよりも優れている理由については、以下の私の回答を参照してください。
Matt

1
B. Goetzによるきちんとした説明InterruptedExceptionは、ibm.com/developerworks/library/j-jtp05236にあります。
ダニエル

InterruptedExceptionは問題ではありません。投稿されたコードでの唯一の問題は、エラーとしてログに記録してはならないことです。興味がある場合にのみ発生したことを示すためのデバッグを除いて、すべてをログに記録する説得力のある理由はありません。 。選択した回答は、スリープや待機などの短い通話を切断できないため、残念です。
Nathan Hughes

回答:


173

ではIndexProcessorクラスは、変数に似て、それが終了する必要があることをスレッドに通知フラグを設定する方法、必要なrunあなただけのクラススコープで使用されていることを。

スレッドを停止する場合は、このフラグを設定join()してスレッドを呼び出し、スレッドが終了するのを待ちます。

揮発性変数を使用するか、フラグとして使用されている変数と同期するゲッターメソッドとセッターメソッドを使用して、フラグがスレッドセーフであることを確認します。

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                running = false;
            }
        }

    }
}

次にSearchEngineContextListener

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        runnable = new IndexProcessor();
        thread = new Thread(runnable);
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            runnable.terminate();
            thread.join();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

3
私があなたがそれを編集したのを見る前に、あなたがあなたの答えで例を挙げたのと全く同じことをしました。正解です。ありがとうございます。これですべてが完璧に機能します:)
Paulius Matulionis

1
スレッドロジックが複雑で、他のクラスの多くのメソッドを呼び出す場合はどうなりますか?ブールフラグをどこでもチェックすることはできません。次に何をすべきか?
2012年

Runnableへのシグナルによってスレッドが終了するような方法でコードを作成できるように、コードデザインを変更する必要があります。ほとんどの用途では、runメソッドにこのループがあるため、通常は問題はありません。
DrYap

3
join()ステートメントがInterruptedExceptionをスローするとどうなりますか?
benzaita 2014年

14
悪いアドバイスを広めるために反対票を投じた。手巻きフラグアプローチは、アプリケーションがスリープが終了するまで待機する必要があることを意味します。これを変更してThread#interruptを使用するのは簡単です。
Nathan Hughes

298

使用することThread.interrupt()は、これを行うための完全に許容できる方法です。実際、上記のようにフラグよりもおそらく好ましいでしょう。その理由は、割り込み可能なブロッキング呼び出し(Thread.sleepjava.nioチャネル操作など)を使用している場合、実際にはそれらをすぐに抜けることができるためです。

フラグを使用する場合は、ブロッキング操作が完了するまで待機する必要があり、フラグを確認できます。とにかくこれを行う必要がある場合があります。たとえば、標準のInputStream/ OutputStreamを使用していて、割り込みできない場合などです。

その場合、スレッドが中断されてもIOは中断されませんが、これはコード内で日常的に簡単に行うことができます(安全に停止してクリーンアップできる重要なポイントでこれを行う必要があります)。

if (Thread.currentThread().isInterrupted()) {
  // cleanup and stop execution
  // for example a break in a loop
}

私が言ったように、主な利点Thread.interrupt()は、割り込み可能な呼び出しからすぐに抜け出せることです。これは、フラグアプローチではできません。


32
+1-Thread.interupt()は、アドホックフラグを使用して同じものを実装するよりも確実に推奨されます。
スティーブンC

2
これは完璧で効率的な方法だと私は思います。+1
RoboAlex 2013

4
コードに小さなタイプミスがあり、Thread.currentThread()には括弧がありません。
Vlad V

1
スレッドと接触する他の誰かがどこか他の場所からスレッドに割り込みをかけ、停止してデバッグが非常に困難になる可能性があるため、実際にはフラグを使用することはお勧めできません。常にフラグも使用してください。
JohnyTex

この特定のケースでは呼び出しinterrupt()は問題ないかもしれませんが、他の多くのケースではそうではありません(たとえば、リソースを閉じる必要がある場合)。ループの内部動作を変更interrupt()した場合は、ブール式に変更することを忘れないでください。私は最初から安全な方法でフラグを使います。
m0skit0 2016

25

簡単な答え:スレッドを2つの一般的な方法のいずれかで内部的に停止できます。

  • runメソッドはreturnサブルーチンにヒットします。
  • Runメソッドが終了し、暗黙的に戻ります。

スレッドを完全に停止することもできます。

  • 呼び出しsystem.exit(これにより、プロセス全体が強制終了されます)
  • スレッドオブジェクトのinterrupt()メソッドを呼び出す*
  • 動作するように聞こえる実装されたメソッドがスレッドにあるかどうかを確認します(kill()またはのようにstop()

*:スレッドを停止することが想定されています。ただし、これが発生したときにスレッドが実際に行うことは、開発者がスレッド実装を作成したときに書き込んだ内容に完全に依存しています。

runメソッドの実装でよく見られるパターンはです。while(boolean){}ブール値は通常、名前が付けられたものisRunningであり、そのスレッドクラスのメンバー変数であり、揮発性であり、通常、他のスレッドからソートのセッターメソッド(例:)によってアクセスできますkill() { isRunnable=false; }。これらのサブルーチンは、スレッドが終了前に保持しているすべてのリソースを解放できるため、優れています。


3
「これらのサブルーチンは、スレッドが終了する前に、保持しているリソースを解放できるので便利です。」わかりません。「公式」の割り込みステータスを使用して、スレッドの保持リソースを完全にクリーンアップできます。Thread.currentThread()。isInterrupted()またはThread.interrupted()(必要に応じて)を使用して確認するか、InterruptedExceptionをキャッチしてクリーンアップします。問題はどこにありますか?
Franz D.

フラグメソッドが機能する理由を理解できませんでした。実行が戻ると停止することを理解していなかったためです!!! これはとてもシンプルでした、これを指摘してくれてありがとうございます、誰も明示的にこれをしていませんでした。
thahgr

9

run()ループ内のフラグ(存在する場合)をチェックして、常にスレッドを終了してください。

スレッドは次のようになります。

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean execute;

    @Override
    public void run() {
        this.execute = true;
        while (this.execute) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                this.execute = false;
            }
        }
    }

    public void stopExecuting() {
        this.execute = false;
    }
}

次に、を呼び出してスレッドを終了できますthread.stopExecuting()。このようにしてスレッドはクリーンに終了しますが、これには最大15秒かかります(スリープのため)。本当に緊急の場合でも、thread.interrupt()を呼び出すことができますが、推奨される方法は常にフラグをチェックすることです。

15秒間待機しないようにするには、次のようにスリープを分割できます。

        ...
        try {
            LOGGER.debug("Sleeping...");
            for (int i = 0; (i < 150) && this.execute; i++) {
                Thread.sleep((long) 100);
            }

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...

2
それはではないThread-それは実装するRunnable-あなたがそれを呼び出すことができない場合Threadとして宣言しない限り、あなたはそれにメソッドを呼び出すThreadことができないstopExecuting()
Don Cheadle

7

通常、スレッドは中断されると終了します。では、なぜネイティブのブール値を使用しないのですか?isInterrupted()を試してください:

Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }   
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

ref- どうすればスレッドを強制終了できますか?stop();を使用せずに


5

スレッドの同期にCountDownLatchは、実行中のプロセスが完了するまでスレッドが待機するのに役立つを使用することをお勧めします。この場合、ワーカークラスは指定されたCountDownLatchカウントのインスタンスで設定されます。呼び出しawait現在のカウントに達するが、の呼び出しのためにゼロまで、この方法は、ブロックされcountDownた方法またはタイムアウトセットに到達します。このアプローチでは、指定された待機時間が経過するのを待たずに、スレッドを即座に中断できます。

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    private final CountDownLatch countdownlatch;
    public IndexProcessor(CountDownLatch countdownlatch) {
        this.countdownlatch = countdownlatch;
    }


    public void run() {
        try {
            while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) {
                LOGGER.debug("Processing...");
            }
        } catch (InterruptedException e) {
            LOGGER.error("Exception", e);
            run = false;
        }

    }
}

あなたが他のスレッドの実行を終了したい場合は、上でカウントダウンを実行CountDownLatchし、joinメインスレッドへのスレッド:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;
    private CountDownLatch countdownLatch = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        countdownLatch = new CountDownLatch(1);
        Thread thread = new Thread(new IndexProcessor(countdownLatch));
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (countdownLatch != null) 
        {
            countdownLatch.countDown();
        } 
        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
            }
            LOGGER.debug("Thread successfully stopped.");
        } 
    }
}

3

いくつかの補足情報。フラグと割り込みの両方がJavaドキュメントで推奨されています。

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

private volatile Thread blinker;

public void stop() {
    blinker = null;
}

public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

長い間(たとえば、入力を)待機するスレッドの場合は、次を使用します。 Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }

3
InterruptedExceptionを無視しないでください。これは、他のコードが明示的にスレッドに終了を要求していることを意味します。その要求を無視するスレッドは、不正なスレッドです。InterruptedExceptionを処理する正しい方法は、ループを終了することです。
VGR 2018年

2

私はAndroidで割り込みを機能させなかったので、この方法を使用して完全に動作しました。

boolean shouldCheckUpdates = true;

private void startupCheckForUpdatesEveryFewSeconds() {
    threadCheckChat = new Thread(new CheckUpdates());
    threadCheckChat.start();
}

private class CheckUpdates implements Runnable{
    public void run() {
        while (shouldCheckUpdates){
            System.out.println("Do your thing here");
        }
    }
}

 public void stop(){
        shouldCheckUpdates = false;
 }

shouldCheckUpdatesはでないため、これは失敗する可能性がありvolatileます。docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3を参照してください。
VGR 2018年

0

onDestroy()/ contextDestroyed()で1000回試すこともあります

      @Override
    protected void onDestroy() {
        boolean retry = true;
        int counter = 0;
        while(retry && counter<1000)
        {
            counter++;
            try{thread.setRunnung(false);
                thread.join();
                retry = false;
                thread = null; //garbage can coll
            }catch(InterruptedException e){e.printStackTrace();}
        }

    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.