2010-04-27 7 views
6

Ich verwende einen Thread, der kontinuierlich aus einer Warteschlange liest.Schleife in Java stoppen

Etwas wie:

public void run() { 
    Object obj; 
    while(true) { 
     synchronized(objectsQueue) { 
      if(objectesQueue.isEmpty()) { 
       try { 
        objectesQueue.wait(); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 

       obj = objectesQueue.poll(); 
      } 
     } 

     // Do something with the Object obj 
    } 
} 

Was ist der beste Weg, um diesen Thread zu stoppen?

Ich sehe zwei Möglichkeiten:

1 - Da Thread.stop() veraltet ist, kann ich eine stopThisThread() Methode implementieren, die eine n Atom-Check-Zustandsgröße verwendet.

2 - Senden Sie ein Death Event-Objekt oder etwas Ähnliches an die Warteschlange. Wenn der Thread ein Todesereignis abruft, wird er beendet.

Ich bevorzuge den 1. Weg, jedoch weiß ich nicht, wann die stopThisThread() Methode aufrufen, da etwas auf seinem Weg zur Warteschlange sein könnte und das Stoppsignal zuerst ankommen kann (nicht wünschenswert).

Irgendwelche Vorschläge?

+3

Hinweis, Ihre 'wait' in einer' while' Schleife sein soll, nicht ein 'if', gegen falsche Wakeups zu schützen. –

+0

An alle, die auf Interrupt angewiesen sind: Ich möchte nur sagen, dass das Leben nie so einfach ist und es Zeiten gibt, in denen man Interrupt auf eine nicht unterbrechbare "Aktivität" aufruft und diese Aktivität den Interrupt absolut nicht wahrnimmt. Dies aus Erfahrung. Trotzdem kenne ich keinen besseren Weg ... Bitte werfen Sie einen genauen Blick auf die API: http://java.sun.com/javase/6/docs/api/index.html?java/lang/Thread .html check out die interrupt() Methode – Yaneeve

+0

@halfwarp: +1 und +1 auf Stephen C's Antwort. Ihr * "Todesereignis" * wird eigentlich als * "Giftpille" * bezeichnet: Sie fügen einfach ein spezielles Objekt (die Giftpille) in die Warteschlange ein, das heißt "stop the thread", sobald Sie es aus der Warteschlange genommen haben. Auf diese Weise wird alles, was sich in der Warteschlange befindet, noch vor der Giftpille verarbeitet. – SyntaxT3rr0r

Antwort

6

Der Ansatz DeathEvent (oder wie es oft genannt wird, "Poison Pill") funktioniert gut, wenn Sie alle Arbeiten in der Warteschlange vor dem Herunterfahren abschließen müssen. Das Problem ist, dass dies lange dauern kann.

Wenn Sie wollen so schnell wie möglich zu stoppen, empfehle ich Ihnen dieses

BlockingQueue<O> queue = ... 

... 

public void run() { 
    try { 
     // The following test is necessary to get fast interrupts. If 
     // it is replaced with 'true', the queue will be drained before 
     // the interrupt is noticed. (Thanks Tim) 
     while (!Thread.interrupted()) { 
      O obj = queue.take(); 
      doSomething(obj); 
     } 
    } catch (InterruptedException ex) { 
     // We are done. 
    } 
} 

tun t den Faden zu stoppen, die mit dieser Methode run instanziiert, einfach t.interrupt(); nennen. Wenn Sie den obigen Code mit anderen Antworten vergleichen, werden Sie feststellen, wie die Verwendung einer BlockingQueue und Thread.interrupt() die Lösung vereinfacht.

Ich würde auch behaupten, dass eine zusätzliche stop Flag ist unnötig, und im großen Bild, potenziell schädlich. Ein gut erzogener Arbeitsthread sollte einen Interrupt berücksichtigen. Ein unerwarteter Interrupt bedeutet einfach, dass der Worker in einem Kontext ausgeführt wird, den der ursprüngliche Programmierer nicht erwartet hat. Das Beste ist, wenn der Arbeiter das tut, was ihm gesagt wird ... d. H. Er sollte aufhören ... ob das zur ursprünglichen Konzeption des Programmierers passt oder nicht.

+0

@Stephen C: +1 ... Zusätzlich zu JCIP empfiehlt es sich, wenn Sie einen unerwarteten Interrupt erhalten, den unterbrochenen Status mit einem * if (interrupted) {Thread.currenThread(). Interrupt()) wiederherzustellen. } * konstruiere in einem * finally * -Block. Hmmmm, vielleicht ist das der bessere Weg, sowohl nach dem Empfang eines unerwarteten Interrupts als auch nach dem Wiederherstellen des unterbrochenen Status zu beenden und dann * auch * einen "sauberen" Stopp zu erlauben, indem man eine * stopThisThread() * Methode benutzt oder? – SyntaxT3rr0r

+0

@WizardOfOdds - Ich folge dir nicht. 1) Es gibt keinen Punkt in der "run" -Methode, die 'interrupt()' aufruft, um das Flag wiederherzustellen. Der Thread, der ihn aufgerufen hat, ist im Begriff zu sterben. 2) Nichts, was Sie gerade gesagt haben erklärt, warum Stop-by-Interrupt schlecht ist oder warum ein "sauberer" Stop-Mechanismus besser ist. (Und eine JCIP-Empfehlung ist * keine * Erklärung.) –

+0

@Stephen C: JCIP Seite 142, da ist Ihre "Erklärung". Übrigens habe ich nicht so getan, als hätte ich irgendeine Erklärung und habe meinen Kommentar mit einem Fragezeichen '?' falls Sie es nicht bemerkt haben :) – SyntaxT3rr0r

0

Ansatz 1 ist der bevorzugte Ansatz.

Setzen Sie einfach ein flüchtiges stop Feld auf True und rufen Sie auf dem laufenden Thread. Dadurch werden alle E/A-Methoden, die auf die Rückgabe warten, mit InterruptedException erzwungen (und wenn Ihre Bibliothek korrekt geschrieben ist, wird dies ordnungsgemäß gehandhabt).

+2

Könnte der Down-Voter bitte kommentieren, was mit dieser Antwort nicht stimmt? –

+1

Ich habe gerade abgestimmt. Mein Argument ist, dass es unnötig ist, ein separates Stopp-Feld zu verwenden. Nebenläufigkeit ist ein schwieriges Thema. Auch wenn Ihre Antwort technisch nicht falsch ist, passt sie auch nicht zu einer bewährten Vorgehensweise. Ich denke, es liegt im Interesse der Community, die Anzahl der Antworten einzuschränken, die zwar funktionieren, aber nicht zu einer optimalen Vorgehensweise passen. –

+0

@TimBender Ich stimme respektvoll nicht zu. Das Verwenden einer stop() - Methode für ein Warteschlangen-Workerobjekt, das einfach ein boolesches Feld setzt und den Thread unterbricht, ist nach jedem Community-Konsens eine "Best Practice". – nsayer

0

Ich denke, Ihre beiden Fälle zeigen tatsächlich das gleiche potenzielle Verhalten. Für den zweiten Fall wird in Thread A das DeathEvent hinzugefügt, nach dem Thread B ein FooEvent hinzufügt. Wenn dein Job-Thread das DeathEvent empfängt, ist immer noch ein FooEvent dahinter, das gleiche Szenario, das du in Option 1 beschreibst, es sei denn, du versuchst die Warteschlange vor der Rückkehr zu löschen, aber dann hältst du den Thread am Leben versuchen zu tun ist es zu stoppen.

Ich stimme Ihnen zu, dass die erste Option wünschenswerter ist. Eine mögliche Lösung hängt davon ab, wie Ihre Warteschlange ausgefüllt ist.Wenn es ein Teil Ihrer Arbeit Thread-Klasse ist könnten Sie Ihre stopThisThread() -Methode einen Flag, das einen entsprechenden Wert (oder throw Exception) aus dem Einreihen Aufruf dh zurückkehren würde:

MyThread extends Thread{ 
    boolean running = true; 

    public void run(){ 
    while(running){ 
     try{ 
     //process queue... 
     }catch(InterruptedExcpetion e){ 
     ... 
     } 
    } 
    } 

    public void stopThisThread(){ 
    running = false; 
    interrupt(); 
    } 

    public boolean enqueue(Object o){ 
    if(!running){ 
     return false; 
     OR 
     throw new ThreadNotRunningException(); 
    } 
    queue.add(o); 
    return true; 
    } 
} 

wäre es dann die Verantwortlichkeit des Objekts, das versucht, das Ereignis in die Warteschlange einzureihen, um es angemessen zu behandeln, aber zumindest wird es wissen, dass das Ereignis nicht in der Warteschlange ist und nicht verarbeitet wird.

+0

Ihr Ansatz ist falsch, wenn Sie die laufende Variable nicht als flüchtig definieren. Es wird auch nicht empfohlen, Threads zu erweitern, sondern Callable oder Runnable zu implementieren, so dass Sie keine Interrupt-Funktion in Ihrem Bereich haben. – Roman

0

Ich habe normalerweise eine Flagge in der Klasse, die den Thread drin hat und in meinem Thread-Code würde ich tun. (HINWEIS: Statt while (true) mache ich while (flag))

Dann erstellen Sie eine Methode in der Klasse, um das Flag auf false zu setzen;

private volatile bool flag = true; 

public void stopThread() 
{ 
    flag = false; 
} 

    public void run() { 
     Object obj; 
     while(flag) { 
      synchronized(objectsQueue) { 
       if(objectesQueue.isEmpty()) { 
        try { 
         objectesQueue.wait(); 
        } catch (InterruptedException e) { 
         e.printStackTrace(); 
        } 

        obj = objectesQueue.poll(); 
       } 
      } 

      // Do something with the Object obj 
     } 
    } 
+0

Das sieht für mich nicht threadsicher aus. Die 'flag'-Variable wird in verschiedenen Threads ohne ordnungsgemäße Synchronisation gelesen und geschrieben. –

+2

@Romain Hippeau: Ich habe deine boolesche Flagge * volatile * gemacht, sonst, wie Stephen C sagte, ist dein Code nicht richtig synchronisiert. – SyntaxT3rr0r

+0

@WizardOfOdds - Vielen Dank für die Korrektur –

1

In Ihrem Leser Thread haben eine boolesche Variable zu stoppen. Wenn Sie möchten, dass dieser Thread stoppt, setzen Sie thius auf "true" und unterbrechen Sie den Thread. Innerhalb des Leserthreads, wenn es sicher ist (wenn Sie kein unverarbeitetes Objekt haben), überprüfen Sie den Status der Stoppvariablen und kehren Sie aus der Schleife zurück, falls gesetzt. wie unten.

public class readerThread extends Thread{ 
    private volitile boolean stop = false; 
    public void stopSoon(){ 
     stop = true; 
     this.interrupt(); 
    } 
    public void run() { 
     Object obj; 
     while(true) { 
      if(stop){ 
       return; 
      } 
      synchronized(objectsQueue) { 
      if(objectesQueue.isEmpty()) { 
       try { 
        objectesQueue.wait(); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
       if(stop){ 
        return; 
       }  
       obj = objectesQueue.poll(); 
       // Do something with the Object obj 
      } 
     } 
    } 


} 
public class OtherClass{ 
    ThreadReader reader; 
    private void start(){ 
      reader = ...; 
      reader.start(); 
    } 

    private void stop(){ 
      reader.stopSoon(); 
      reader.join();  // Wait for thread to stop if nessasery. 
    } 
} 
+0

So habe ich einen Warteschlangenarbeiter klassisch codiert. Der wesentliche Unterschied zwischen diesem Ansatz und der akzeptierten Antwort besteht darin, dass die "Poison Pill" -Methode sicherstellt, dass die Warteschlange vor dem Beenden vollständig verarbeitet wird, anstatt sie aufzugeben. Welcher dieser Ansätze richtiger ist, hängt vom Problemraum ab. Sie können auch in Erwägung ziehen, nach dem Interrupt() eine Join() -Option hinzuzufügen, die stopSoon() blockiert, bis der Thread tatsächlich abstirbt. – nsayer

1

Warum nicht einen Scheduler verwenden, den Sie bei Bedarf einfach anhalten können? Der Standard-Scheduler unterstützt wiederholte Planung, die auch darauf wartet, dass der Worker-Thread beendet wird, bevor ein neuer Lauf neu terminiert wird.

ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); 
service.scheduleWithFixedDelay(myThread, 1, 10, TimeUnit.SECONDS); 

Diese Probe würde den Thread mit einer Verzögerung von 10 Sekunden ausgeführt, das heißt, wenn ein Lauf beendet ist, es es 10 Sekunden später neu gestartet wird. Und anstatt das Rad neu zu erfinden, müssen Sie das Rad neu erfinden

Die while (true) ist nicht mehr notwendig.

ScheduledExecutorService Javadoc

+1

'service.shutdown()' ähnelt dem Poison-Pill-Ansatz in dem Sinne, dass keine weiteren Tasks mehr akzeptiert werden, Tasks weiter ausgeführt werden und zuvor akzeptierte Tasks ausgeführt werden. Im Gegensatz dazu wird 'service.shutdownNow()' versuchen, die Ausführung von Aufgaben zu stoppen, und zuvor akzeptierte Aufgaben werden nicht ausgeführt. – user454322