Java iş parçacığı düzgün bir şekilde nasıl durdurulur?


276

Düzgün Java iş parçacığı durdurmak için bir çözüm gerekiyor.

IndexProcessorRunnable arabirimini uygulayan sınıf var :

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

    }
}

Ve ben ServletContextListeneriplik başlar ve durduran sınıf var :

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

Ancak Tomcat'i kapattığımda, IndexProcessor sınıfımda istisna alıyorum:

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 kullanıyorum. Soru şu:

İş parçacığını nasıl durdurabilirim ve herhangi bir istisna atmayabilirim?

Not: Kullanım dışı .stop();olduğundan yöntemi kullanmak istemiyorum .


1
Bir iş parçacığını yarıya kadar sonlandırmak her zaman bir istisna oluşturur. Normal bir davranışsa, sadece yakalayıp yok sayabilirsiniz InterruptedException. Ben böyle düşünüyorum, ama aynı zamanda standart yolun nasıl olduğunu merak ediyorum.
nhahtdh

Ben iş parçacıkları oldukça yeni bu yüzden çok sık iş parçacıkları kullanmıyorum, bu yüzden istisna göz ardı normal davranış olup olmadığını bilmiyorum. Bu yüzden soruyorum.
Paulius Matulionis

Çoğu durumda, istisnayı görmezden gelmek ve yöntem işlemeyi sonlandırmak normal bir davranıştır. Bunun neden bayrak tabanlı bir yaklaşımdan daha iyi olduğunu öğrenmek için aşağıdaki cevabımı görün.
Matt

1
B. Goetz tarafından ilgili düzgün bir açıklama ibm.com/developerworks/library/j-jtp05236 adresindeInterruptedException bulunabilir .
Daniel

InterruptedException bir sorun değildir, yayınlanan koddaki tek sorununuz bir hata olarak günlüğe kaydetmemelisiniz, gerçekten ilgilendiğinizde olduğunu göstermek için hata ayıklama dışında her şey gibi oturum açmanın gerçekten zorlayıcı bir nedeni yoktur. . seçilen cevap talihsizdir, çünkü uyku ve bekleme gibi çağrılara kısa çağrılar yapılmasına izin vermez.
Nathan Hughes

Yanıtlar:


173

Gelen IndexProcessorsınıfının bunu değişkene benzer, sonlandırmak için ihtiyaç duyacağı iplik bildiren bir bayrak ayarlamasını bir yol gerekir runsadece sınıf kapsamında kullanmış olduğu.

İş parçacığını durdurmak istediğinizde, bu bayrağı ayarlar ve join()iş parçacığını arar ve bitmesini beklersiniz.

Geçici bir değişken kullanarak veya bayrak olarak kullanılan değişkenle senkronize edilen alıcı ve ayarlayıcı yöntemlerini kullanarak bayrağın iş parçacığı güvenli olduğundan emin olun.

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

    }
}

Sonra 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
Düzenlediğinize bakmadan hemen önce cevabınızdaki örnekleri verdiğinizle aynı şeyi yaptım. Mükemmel cevap! Teşekkürler, şimdi her şey mükemmel çalışıyor :)
Paulius Matulionis

1
İş parçacığı mantığı karmaşıksa ve diğer sınıfların birçok yöntemini çağırıyorsa ne olur? Boole bayrağını her yerde kontrol etmek mümkün değildir. O zaman ne yapmalı?
Soteric

Kod tasarımını, Runnable'a giden bir sinyalin iş parçacığının çıkmasına neden olacak şekilde oluşturmanız için değiştirmeniz gerekir. Çoğu kullanımda bu döngü çalışma yönteminde olduğundan, genellikle sorun yoktur.
DrYap

3
Join () ifadesi bir InterruptedException atarsa ​​ne olur?
benzaita

14
Kötü tavsiye yaymak için aşağı oy. elle haddelenmiş bayrak yaklaşımı, uygulamanın uykunun bitmesini beklemek zorunda olduğu anlamına gelir; burada kesinti uyku kısa keser. Thread # interrupt kullanmak için bunu değiştirmek kolay olurdu.
Nathan Hughes

298

Kullanmak Thread.interrupt()bunu yapmanın mükemmel kabul edilebilir bir yoludur. Aslında, yukarıda önerildiği gibi bir bayrağa tercih edilebilir. Bunun nedeni, kesintisiz bir engelleme çağrısındaysanız (Thread.sleep java.nio Kanal işlemlerini veya kullanmak ), hemen bu işlemlerden çıkabilmenizdir.

Bir bayrak kullanırsanız, engelleme işleminin bitmesini beklemeniz gerekir ve ardından bayrağınızı kontrol edebilirsiniz. Bazı durumlarda bunu standart olarak kullanmak gibi bir şekilde yapmanız gerekir.InputStream / OutputStreamkesintiye uğramayan .

Bu durumda, bir iş parçacığı kesintiye uğradığında, IO'yu kesmez, ancak bunu kodunuzda rutin olarak kolayca yapabilirsiniz (ve bunu güvenli bir şekilde durdurabileceğiniz ve temizleyebileceğiniz stratejik noktalarda yapmalısınız)

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

Dediğim gibi, ana avantajı Thread.interrupt(), bayrak yaklaşımı ile yapamayacağınız kesintili çağrıları hemen kesebilmenizdir.


32
+1 - Thread.interupt () kesinlikle aynı şeyi geçici bir bayrak kullanarak uygulamak için tercih edilir.
Stephen C

2
Ben de bunu yapmanın mükemmel ve etkili bir yol olduğunu düşünüyorum. +1
RoboAlex

4
Kodda küçük bir yazım hatası var, Thread.currentThread () parantez yok.
Vlad V

1
Aslında bir bayrak kullanmak tercih edilmez çünkü iplikle temas eden bir başkası, başka bir yerden kesebilir ve durmasına ve hata ayıklaması çok zor olabilir. Daima bir bayrak kullanın.
JohnyTex

Bu özel durumda çağrı iyi interrupt()olabilir, ancak diğer birçok durumda bu uygun değildir (örneğin bir kaynağın kapatılması gerekiyorsa). Birisi döngünün iç çalışmasını değiştirirse interrupt(), boolean yolunu değiştirmeyi hatırlamanız gerekir . En başından beri güvenli yolu seçer ve bayrağı kullanırdım.
m0skit0

25

Basit yanıt: Bir iş parçacığını DAHİLİ olarak iki yaygın yoldan biriyle durdurabilirsiniz:

  • Run yöntemi bir dönüş alt yordamına çarpar.
  • Çalıştırma yöntemi biter ve dolaylı olarak geri döner.

Ayrıca konuları HARİCİ olarak durdurabilirsiniz:

  • Aramak system.exit (bu, tüm sürecinizi öldürür)
  • İş parçacığı nesnesinin interrupt() yöntemini *
  • İş parçacığının, işe yarayacak gibi ( kill()veya gibi stop()) uygulanmış bir yöntemi olup olmadığını görün

*: Beklenti, bunun bir iş parçacığını durdurması gerekiyor. Ancak, iş parçacığı bu olduğunda aslında ne yapar tamamen geliştirici iş parçacığı uygulaması oluştururken yazdıklarına kalmış.

Çalışma yöntemi uygulamaları ile Gördüğünüz Genel örnek bir olduğunu while(boolean){}boolean tipik adlandırılmış bir şey olduğunda, isRunningbu onun iplik sınıfının bir üyesi değişkeni, bu uçucu olduğunu, ve örneğin bir çeşit kurucu metod, diğer iş parçacıkları tarafından genellikle erişilebilir kill() { isRunnable=false; }. Bu alt yordamlar iyidir, çünkü iş parçacığının sonlandırmadan önce sahip olduğu kaynakları serbest bırakmasına izin verirler.


3
"Bu altyordamlar iyidir, çünkü iş parçacığının sonlandırmadan önce sahip olduğu kaynakları serbest bırakmasına izin verirler." Anlamıyorum. "Resmi" kesintiye uğramış durumunu kullanarak bir iş parçacığının tutulan kaynaklarını mükemmel bir şekilde temizleyebilirsiniz. Sadece Thread.currentThread (). İsInterrupted () veya Thread.interrupted () (hangisini ihtiyacınıza uyuyorsa) kullanarak kontrol edin veya InterruptedException yazılımını yakalayın ve temizleyin. Sorun nerede?
Franz

Bayrak isabet neden işe yarayacağını anlayamadım, çünkü çalışma geri döndüğünde durduğunu anlamamıştım! Bu çok basitti, sevgili efendim bunu işaret ettiğiniz için teşekkür ederim, kimse bunu açıkça yapmamıştı.
thahgr

9

run()Döngüdeki bayrağı (varsa) kontrol ederek her zaman konuları sonlandırmalısınız .

Konunuz şöyle görünmelidir:

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

Sonra arayarak iş parçacığı sonlandırabilirsiniz thread.stopExecuting(). Bu şekilde iplik temiz şekilde sonlandırılır, ancak bu 15 saniyenizi alır (uykunuz nedeniyle). Gerçekten acilse thread.interrupt () öğesini çağırabilirsiniz - ancak tercih edilen yol her zaman bayrağı kontrol ediyor olmalıdır.

15 saniye beklemekten kaçınmak için uykuyu şu şekilde bölebilirsiniz:

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

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

2
bu değil Thread- uygular Runnable- bir Threadyöntem olarak ilan etmedikçe yöntemleri çağıramazsınız Thread, bu durumda stopExecuting()
Don Cheadle

7

Genellikle, bir iş parçacığı kesintiye uğradığında sonlandırılır. Peki, neden yerli boole kullanmıyorsunuz? İsInterrupted () yöntemini deneyin:

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- Bir iş parçacığını nasıl öldürebilirim? stop () kullanmadan;


5

İş parçacıklarını senkronize etmek CountDownLatchiçin işlemin tamamlanıncaya kadar iş parçacıklarının beklemesine yardımcı olanları kullanmayı tercih ederim . Bu durumda, işçi sınıfı CountDownLatchbelirli bir sayıya sahip bir örnekle ayarlanır . Bir awaityönteme çağrı, yöntemin çağrılması countDownveya zaman aşımı ayarına ulaşılmasından dolayı geçerli sayı sıfıra ulaşıncaya kadar engellenir . Bu yaklaşım, belirli bir bekleme süresinin geçmesini beklemek zorunda kalmadan bir iş parçacığının anında kesilmesine izin verir:

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

    }
}

Diğer iş parçacığının yürütülmesini bitirmek istediğinizde , ana iş parçacığındaki countDown öğesini CountDownLatchve joiniş parçacığını çalıştırın:

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

Bazı ek bilgiler. Java belgesinde hem bayrak hem de kesme önerilir.

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

Uzun süre bekleyen bir iplik için (örn. Giriş için), Thread.interrupt

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

3
Bir InterruptedException'ı asla yok sayın. Başka bir kod açıkça iş parçacığının sonlanmasını istiyor demektir. Bu isteği yok sayan bir iş parçacığı haydut bir iş parçacığıdır. Bir InterruptedException özelliğinin işlenmesinin doğru yolu döngüden çıkmaktır.
VGR

2

Android'de çalışmak için kesinti alamadım, bu yüzden bu yöntemi kullandım, mükemmel çalışıyor:

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

Başarısız olması muhtemeldir, çünkü shouldCheckUpdates değildir volatile. Bkz. Docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3 .
VGR

0

Bazen onDestroy () / contextDestroyed () içinde 1000 kez deneyeceğim

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

    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.