Bagaimana cara menghentikan Thread di Jawa dengan benar?


276

Saya perlu solusi untuk menghentikan thread dengan benar di Jawa.

Saya memiliki IndexProcessorkelas yang mengimplementasikan antarmuka 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;
            }
        }

    }
}

Dan saya memiliki ServletContextListenerkelas yang memulai dan menghentikan utas:

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.");
        }
    }
}

Tetapi ketika saya mematikan kucing jantan, saya mendapatkan pengecualian di kelas IndexProcessor saya:

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)

Saya menggunakan JDK 1.6. Jadi pertanyaannya adalah:

Bagaimana saya bisa menghentikan utas dan tidak membuang pengecualian?

PS Saya tidak ingin menggunakan .stop();metode karena sudah usang.


1
Mengakhiri setengah jalan utas akan selalu menghasilkan pengecualian. Jika itu perilaku normal, maka Anda bisa langsung menangkap dan mengabaikannya InterruptedException. Inilah yang saya pikirkan, tetapi saya juga bertanya-tanya bagaimana cara standarnya.
nhahtdh

Saya belum pernah menggunakan utas sangat sering jadi saya cukup baru dalam utas, jadi saya tidak tahu apakah itu perilaku normal untuk mengabaikan pengecualian. Itu sebabnya saya bertanya.
Paulius Matulionis

Dalam banyak kasus adalah perilaku normal untuk mengabaikan pengecualian dan menghentikan pemrosesan metode. Lihat jawaban saya di bawah untuk alasan ini lebih baik daripada pendekatan berbasis bendera.
Matt

1
Penjelasan rapi oleh B. Goetz tentang InterruptedExceptiondapat ditemukan di ibm.com/developerworks/library/j-jtp05236 .
Daniel

InterruptedException bukan masalah, satu-satunya masalah Anda dalam kode yang diposting adalah Anda tidak boleh mencatatnya sebagai kesalahan, benar-benar tidak ada alasan kuat untuk mencatatnya semua kecuali sebagai debug hanya untuk menunjukkan hal itu terjadi jika Anda tertarik . jawaban yang dipilih sangat disayangkan karena tidak memungkinkan untuk memotong panggilan pendek ke panggilan seperti tidur dan menunggu.
Nathan Hughes

Jawaban:


173

Di IndexProcessorkelas Anda memerlukan cara mengatur bendera yang menginformasikan utas bahwa ia harus diakhiri, mirip dengan variabel runyang telah Anda gunakan hanya dalam lingkup kelas.

Ketika Anda ingin menghentikan utas, Anda mengatur bendera ini dan memanggil join()utas dan menunggu sampai selesai.

Pastikan bahwa flag tersebut aman dengan menggunakan variabel volatile atau dengan menggunakan metode pengambil dan penyetel yang disinkronkan dengan variabel yang digunakan sebagai bendera.

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;
            }
        }

    }
}

Kemudian di 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
Saya telah melakukan hal yang persis sama ketika Anda memberikan contoh dalam jawaban Anda tepat sebelum saya melihat bahwa Anda telah mengeditnya. Jawaban bagus! Terima kasih, sekarang semuanya bekerja dengan baik :)
Paulius Matulionis

1
Bagaimana jika logika utas itu rumit dan memanggil banyak metode dari kelas lain? Bendera boolean tidak mungkin diperiksa di mana pun. Lalu apa yang harus dilakukan?
Soteric

Anda harus mengubah desain kode sehingga Anda membangunnya dengan cara di mana sinyal ke Runnable akan menyebabkan utas keluar. Sebagian besar kegunaan memang memiliki loop ini dalam menjalankan metode sehingga biasanya tidak ada masalah.
DrYap

3
Apa yang terjadi jika pernyataan join () melempar InterruptedException?
benzaita

14
Diturunkan karena menyebarkan saran buruk. pendekatan bendera linting tangan berarti aplikasi harus menunggu sampai tidur selesai, di mana gangguan akan mempersingkat waktu tidur. Akan mudah untuk memodifikasi ini menggunakan interupsi Thread #.
Nathan Hughes

298

Menggunakan Thread.interrupt()adalah cara yang bisa diterima untuk melakukan ini. Bahkan, mungkin lebih disukai daripada bendera seperti yang disarankan di atas. Alasannya adalah bahwa jika Anda berada di panggilan pemblokiran interruptable (suka Thread.sleepatau menggunakan operasi java.nio Channel), Anda benar-benar dapat keluar dari mereka segera.

Jika Anda menggunakan bendera, Anda harus menunggu operasi pemblokiran selesai dan kemudian Anda dapat memeriksa bendera Anda. Dalam beberapa kasus Anda tetap harus melakukan ini, seperti menggunakan standar InputStream/ OutputStreamyang tidak dapat disela.

Dalam hal itu, ketika utas terputus, itu tidak akan mengganggu IO, namun, Anda dapat dengan mudah melakukan ini secara rutin dalam kode Anda (dan Anda harus melakukan ini di titik-titik strategis di mana Anda dapat berhenti dan membersihkan dengan aman)

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

Seperti yang saya katakan, keuntungan utama Thread.interrupt()adalah bahwa Anda dapat segera keluar dari panggilan interupsi, yang tidak dapat Anda lakukan dengan pendekatan bendera.


32
+1 - Thread.interupt () jelas lebih disukai untuk mengimplementasikan hal yang sama menggunakan flag ad-hoc.
Stephen C

2
Saya juga berpikir bahwa ini cara yang sempurna dan efisien untuk melakukannya. +1
RoboAlex

4
Ada kesalahan ketik kecil dalam kode, Thread.currentThread () tidak memiliki tanda kurung.
Vlad V

1
Sebenarnya itu tidak disukai menggunakan bendera karena orang lain yang bersentuhan dengan utas dapat melakukan interupsi darinya dari tempat lain, menyebabkannya berhenti dan menjadi sangat sulit untuk di-debug. Selalu gunakan bendera juga.
JohnyTex

Dalam kasus khusus ini panggilan interrupt()mungkin baik-baik saja, tetapi dalam banyak kasus lain tidak (misalnya jika sumber daya perlu ditutup). Jika ada yang mengubah cara kerja dalam loop, Anda harus ingat untuk mengubah interrupt()ke cara boolean. Saya akan mencari cara yang aman dari awal dan menggunakan bendera.
m0skit0

25

Jawaban sederhana: Anda dapat menghentikan utas secara internal dengan salah satu dari dua cara umum:

  • Metode menjalankan hits subrutin kembali.
  • Menjalankan metode selesai, dan mengembalikan secara implisit.

Anda juga dapat menghentikan utas SECARA EKSTERNAL:

  • Panggil system.exit(ini membunuh seluruh proses Anda)
  • Panggil metode objek utas interrupt()*
  • Lihat apakah utas memiliki metode yang diimplementasikan yang kedengarannya akan berfungsi (suka kill()atau stop())

*: Harapannya adalah bahwa ini seharusnya menghentikan utas. Namun, apa yang sebenarnya dilakukan utas saat ini terjadi sepenuhnya tergantung pada apa yang ditulis pengembang saat mereka membuat implementasi utas.

Pola umum yang Anda lihat dengan implementasi metode run adalah a while(boolean){}, di mana boolean biasanya bernama sesuatu isRunning, itu adalah variabel anggota dari kelas utasnya, itu mudah berubah, dan biasanya dapat diakses oleh utas lain dengan metode penyetel macam, misalnya kill() { isRunnable=false; }. Subrutin-subrutin ini bagus karena memungkinkan utas untuk melepaskan sumber daya yang dimilikinya sebelum diakhiri.


3
"Subrutin ini bagus karena memungkinkan utas untuk melepaskan sumber daya yang dimilikinya sebelum diakhiri." Saya tidak mengerti. Anda dapat membersihkan sumber daya yang dimiliki thread dengan menggunakan status terputus "resmi". Cukup periksa menggunakan Thread.currentThread (). IsInterrupted () atau Thread.interrupted () (mana yang sesuai dengan kebutuhan Anda), atau tangkap InterruptedException, dan bersih-bersih. Dimana masalahnya?
Franz D.

Saya tidak dapat mengerti mengapa metode flag bekerja, karena saya tidak mengerti bahwa itu berhenti ketika run hits kembali !!! Ini sangat sederhana, Tuan terima kasih telah menunjukkan ini, tidak ada yang melakukan ini secara eksplisit.
thahgr

9

Anda harus selalu mengakhiri utas dengan memeriksa bendera di run()loop (jika ada).

Utas Anda akan terlihat seperti ini:

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;
    }
}

Kemudian Anda dapat mengakhiri utas dengan menelepon thread.stopExecuting(). Dengan begitu utas berakhir bersih, tetapi ini membutuhkan waktu hingga 15 detik (karena Anda tidur). Anda masih dapat memanggil thread.interrupt () jika itu benar-benar mendesak - tetapi cara yang lebih disukai harus selalu memeriksa bendera.

Untuk menghindari menunggu selama 15 detik, Anda dapat membagi tidur seperti ini:

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

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

2
itu bukan Thread- itu mengimplementasikan Runnable- Anda tidak dapat memanggil Threadmetode di atasnya kecuali Anda menyatakannya sebagai Threaddalam hal mana Anda tidak dapat memanggilstopExecuting()
Don Cheadle

7

Biasanya, utas diakhiri ketika terputus. Jadi, mengapa tidak menggunakan boolean asli? Coba Terinterupsi ():

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- Bagaimana saya bisa membunuh utas? tanpa menggunakan stop ();


5

Untuk menyinkronkan utas, saya lebih suka menggunakan CountDownLatchutas yang membantu menunggu sampai proses yang dilakukan selesai. Dalam hal ini, kelas pekerja diatur dengan CountDownLatchinstance dengan jumlah yang diberikan. Panggilan ke awaitmetode akan diblokir hingga hitungan saat ini mencapai nol karena doa countDownmetode atau set timeout tercapai. Pendekatan ini memungkinkan menginterupsi utas secara instan tanpa harus menunggu waktu tunggu yang ditentukan untuk berlalu:

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;
        }

    }
}

Saat Anda ingin menyelesaikan eksekusi utas lainnya, jalankan countDown di CountDownLatchdan joinutas ke utas utama:

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

Beberapa info tambahan. Kedua flag dan interrupt disarankan dalam Java doc.

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();
    }
}

Untuk utas yang menunggu dalam waktu lama (mis., Untuk input), gunakan Thread.interrupt

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

3
Jangan pernah abaikan InterruptedException. Ini berarti beberapa kode lain secara eksplisit meminta utas Anda untuk mengakhiri. Utas yang mengabaikan permintaan itu adalah utas nakal. Cara yang benar untuk menangani InterruptedException adalah keluar dari loop.
VGR

2

Saya tidak mendapatkan interupsi untuk bekerja di Android, jadi saya menggunakan metode ini, bekerja dengan sempurna:

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;
 }

Ini kemungkinan gagal, karena seharusnyaCheckUpdates tidak volatile. Lihat docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3 .
VGR

0

Kadang saya akan mencoba 1000 kali di onDestroy () / contextDestroyed () saya

      @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();}
        }

    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.