Frage

Beim Schreiben von Multithread-Anwendungen sind Race Conditions eines der häufigsten Probleme.

Meine Fragen an die Community sind:

Was ist eine Rennbedingung?Wie erkennt man sie?Wie gehst du damit um?Und schließlich: Wie verhindern Sie, dass sie auftreten?

War es hilfreich?

Lösung

Eine Race-Bedingung tritt auf, wenn zwei oder mehr Threads auf gemeinsam genutzte Daten zugreifen können und versuchen, diese gleichzeitig zu ändern.Da der Thread-Planungsalgorithmus jederzeit zwischen Threads wechseln kann, wissen Sie nicht, in welcher Reihenfolge die Threads versuchen, auf die gemeinsam genutzten Daten zuzugreifen.Daher hängt das Ergebnis der Datenänderung vom Thread-Planungsalgorithmus ab, d. h.Beide Threads „rennen“ darum, auf die Daten zuzugreifen bzw. diese zu ändern.

Probleme treten häufig auf, wenn ein Thread ein „Prüfen-dann-Handeln“ ausführt (z. B.„Überprüfen“, wenn der Wert X ist, dann „Handeln“, um etwas zu tun, das davon abhängt, dass der WertZ.B:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

Der Punkt ist, dass y 10 oder irgendetwas anderes sein könnte, je nachdem, ob ein anderer Thread x zwischen der Prüfung und der Aktion geändert hat.Du hast keine wirkliche Möglichkeit, es zu wissen.

Um das Auftreten von Race Conditions zu verhindern, sperren Sie normalerweise die gemeinsam genutzten Daten, um sicherzustellen, dass jeweils nur ein Thread auf die Daten zugreifen kann.Das würde etwa Folgendes bedeuten:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x

Andere Tipps

Eine „Race Condition“ liegt vor, wenn Multithread-Code (oder anderweitig paralleler Code), der auf eine gemeinsam genutzte Ressource zugreift, dies auf eine Weise tun könnte, die zu unerwarteten Ergebnissen führt.

Nehmen Sie dieses Beispiel:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

Wenn 5 Threads diesen Code gleichzeitig ausführen würden, würde der Wert von x am Ende NICHT 50.000.000 betragen.Es würde tatsächlich mit jedem Lauf variieren.

Dies liegt daran, dass jeder Thread Folgendes tun muss, damit er den Wert von x erhöhen kann:(natürlich vereinfacht)

Retrieve the value of x
Add 1 to this value
Store this value to x

Jeder Thread kann sich jederzeit in jedem Schritt dieses Prozesses befinden, und sie können aufeinander treten, wenn eine gemeinsam genutzte Ressource beteiligt ist.Der Zustand von x kann von einem anderen Thread während der Zeit zwischen dem Lesen von x und dem Zurückschreiben von x geändert werden.

Nehmen wir an, ein Thread ruft den Wert von x ab, hat ihn aber noch nicht gespeichert.Ein anderer Thread kann das auch abrufen Dasselbe Wert von x (da ihn noch kein Thread geändert hat) und dann würden beide den speichern Dasselbe Wert (x+1) zurück in x!

Beispiel:

Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, Wert ist 7
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: speichert 8 in x

Race-Bedingungen können durch den Einsatz einer Art von vermieden werden Verriegelung Mechanismus vor dem Code, der auf die gemeinsam genutzte Ressource zugreift:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}

Hier lautet die Antwort jedes Mal 50.000.000.

Weitere Informationen zum Sperren finden Sie unter:Mutex, Semaphor, kritischer Abschnitt, gemeinsam genutzte Ressource.

Was ist eine Race Condition?

Sie planen, um 17 Uhr ins Kino zu gehen.Sie erkundigen sich um 16 Uhr nach der Verfügbarkeit der Tickets.Der Vertreter sagt, dass sie verfügbar sind.Sie entspannen sich und erreichen die Kasse 5 Minuten vor der Show.Ich bin sicher, Sie können erraten, was passiert:Es ist ein volles Haus.Das Problem lag hier in der Zeitspanne zwischen der Prüfung und der Aktion.Sie haben um 4 Uhr nachgefragt und um 5 Uhr gehandelt.In der Zwischenzeit hat sich jemand anderes die Tickets geschnappt.Das ist eine Rennbedingung – insbesondere ein „Check-then-Act“-Szenario der Rennbedingungen.

Wie erkennt man sie?

Religiöse Codeüberprüfung, Multithread-Komponententests.Es gibt keine Abkürzung.Es gibt nur wenige Eclipse-Plugins, aber noch nichts Stabiles.

Wie gehen Sie damit um und wie verhindern Sie sie?

Das Beste wäre, nebenwirkungsfreie und zustandslose Funktionen zu erstellen und so viele unveränderliche Funktionen wie möglich zu verwenden.Aber das ist nicht immer möglich.Die Verwendung von java.util.concurrent.atomic, gleichzeitige Datenstrukturen, ordnungsgemäße Synchronisierung und akteurbasierte Parallelität werden daher hilfreich sein.

Die beste Ressource für Parallelität ist JCIP.Sie können auch noch mehr bekommen Details zur obigen Erklärung finden Sie hier.

Es gibt einen wichtigen technischen Unterschied zwischen Rennbedingungen und Datenrennen.Die meisten Antworten scheinen davon auszugehen, dass diese Begriffe gleichwertig sind, aber das ist nicht der Fall.

Ein Datenwettlauf tritt auf, wenn zwei Befehle auf denselben Speicherort zugreifen, mindestens einer dieser Zugriffe ein Schreibzugriff ist und es keinen gibt geschieht vor der Bestellung unter diesen Zugängen.Nun wird viel darüber debattiert, was eine „Ereignis-vor-Bestellung“ ausmacht, aber im Allgemeinen induzieren Ulock-Sperr-Paare auf derselben Sperrvariablen und Wartesignal-Paare auf derselben Bedingungsvariablen eine „Ereignis-vor-Bestellung“.

Eine Racebedingung ist ein semantischer Fehler.Es handelt sich um einen Fehler, der im Timing oder in der Reihenfolge von Ereignissen auftritt und zu einem fehlerhaften Programm führt Verhalten.

Viele Race-Bedingungen können (und werden tatsächlich) durch Datenrennen verursacht werden, aber das ist nicht notwendig.Tatsächlich sind Datenrennen und Rennbedingungen weder die notwendige noch die hinreichende Bedingung füreinander. Das Der Blogbeitrag erklärt den Unterschied auch sehr gut anhand eines einfachen Beispiels einer Banktransaktion.Hier ist eine weitere einfache Beispiel das erklärt den Unterschied.

Nachdem wir nun die Terminologie festgelegt haben, versuchen wir, die ursprüngliche Frage zu beantworten.

Da es sich bei Race Conditions um semantische Fehler handelt, gibt es keine allgemeine Möglichkeit, sie zu erkennen.Dies liegt daran, dass es keine Möglichkeit gibt, über ein automatisiertes Orakel zu verfügen, das zwischen korrekten und korrekten unterscheiden kann.falsches Programmverhalten im allgemeinen Fall.Die Rassenerkennung ist ein unentscheidbares Problem.

Andererseits haben Datenrennen eine genaue Definition, die sich nicht unbedingt auf die Korrektheit bezieht, und daher kann man sie erkennen.Es gibt viele Varianten von Data-Race-Detektoren (statische/dynamische Data-Race-Erkennung, Lockset-basierte Data-Race-Erkennung, auf Vorkommnissen basierende Data-Race-Erkennung, Hybrid-Data-Race-Erkennung).Ein hochmoderner dynamischer Datenrennen-Detektor ist ThreadSanitizer was in der Praxis sehr gut funktioniert.

Der Umgang mit Datenrennen im Allgemeinen erfordert eine gewisse Programmierdisziplin, um Vorgänge zwischen Zugriffen auf gemeinsam genutzte Daten herbeizuführen (entweder während der Entwicklung oder sobald sie mit den oben genannten Tools erkannt werden).Dies kann durch Sperren, Bedingungsvariablen, Semaphoren usw. erfolgen.Man kann jedoch auch andere Programmierparadigmen wie Message Passing (anstelle von Shared Memory) verwenden, die Datenrennen durch Konstruktion vermeiden.

Eine Art kanonische Definition ist „wenn zwei Threads gleichzeitig auf denselben Speicherort im Speicher zugreifen und mindestens einer der Zugriffe ein Schreibzugriff ist.“ In diesem Fall erhält der „Leser“-Thread möglicherweise den alten oder den neuen Wert, je nachdem, welcher Thread „das Rennen gewinnt“. Dies ist nicht immer ein Fehler – tatsächlich tun dies einige wirklich umständliche Low-Level-Algorithmen Zweck – sollte aber grundsätzlich vermieden werden.@Steve Gury gibt ein gutes Beispiel dafür, wann es ein Problem sein könnte.

Eine Race Condition ist eine Art Fehler, der nur unter bestimmten zeitlichen Bedingungen auftritt.

Beispiel:Stellen Sie sich vor, Sie haben zwei Threads, A und B.

Im Thread A:

if( object.a != 0 )
    object.avg = total / object.a

Im Thread B:

object.a = 0

Wenn Thread A unmittelbar nach der Überprüfung, dass object.a nicht null ist, vorzeitig beendet wird, reicht B aus a = 0, und wenn Thread A den Prozessor erhält, führt er eine „Division durch Null“ durch.

Dieser Fehler tritt nur auf, wenn Thread A direkt nach der if-Anweisung vorzeitig beendet wird. Dies ist sehr selten, kann aber vorkommen.

Race-Bedingungen treten in Multithread-Anwendungen oder Multiprozesssystemen auf.Eine Race-Bedingung ist im Grunde alles, was davon ausgeht, dass zwei Dinge, die sich nicht im selben Thread oder Prozess befinden, in einer bestimmten Reihenfolge passieren, ohne dass Schritte unternommen werden, um dies sicherzustellen.Dies geschieht häufig, wenn zwei Threads Nachrichten weiterleiten, indem sie Mitgliedsvariablen einer Klasse festlegen und überprüfen, auf die beide zugreifen können.Es gibt fast immer eine Race-Bedingung, wenn ein Thread Sleep aufruft, um einem anderen Thread Zeit zu geben, eine Aufgabe abzuschließen (es sei denn, dieser Sleep befindet sich in einer Schleife mit einem Prüfmechanismus).

Tools zur Verhinderung von Race Conditions hängen von der Sprache und dem Betriebssystem ab. Einige gebräuchliche Tools sind jedoch Mutexe, kritische Abschnitte und Signale.Mutexe sind gut, wenn Sie sicherstellen möchten, dass Sie der Einzige sind, der etwas tut.Signale sind gut, wenn Sie sicherstellen möchten, dass jemand anderes etwas getan hat.Die Minimierung gemeinsam genutzter Ressourcen kann auch dazu beitragen, unerwartetes Verhalten zu verhindern

Das Erkennen von Rennbedingungen kann schwierig sein, aber es gibt ein paar Anzeichen.Code, der stark auf Sleeps angewiesen ist, ist anfällig für Race Conditions. Überprüfen Sie daher zunächst, ob der betroffene Code Sleep-Aufrufe enthält.Das Hinzufügen besonders langer Ruhezeiten kann auch zum Debuggen verwendet werden, um eine bestimmte Reihenfolge von Ereignissen zu erzwingen.Dies kann nützlich sein, um das Verhalten zu reproduzieren, zu sehen, ob es durch eine Änderung des Timings verschwinden kann, und um implementierte Lösungen zu testen.Die Sleeps sollten nach dem Debuggen entfernt werden.

Das charakteristische Zeichen dafür, dass eine Racebedingung vorliegt, ist jedoch, wenn ein Problem vorliegt, das auf einigen Computern nur zeitweise auftritt.Häufige Fehler wären Abstürze und Deadlocks.Mit der Protokollierung sollten Sie in der Lage sein, den betroffenen Bereich zu finden und von dort aus weiterzuarbeiten.

Race Condition hängt nicht nur mit der Software, sondern auch mit der Hardware zusammen.Eigentlich wurde der Begriff ursprünglich von der Hardware-Industrie geprägt.

Entsprechend Wikipedia:

Der Begriff stammt aus der Idee von Zwei Signale rennen gegeneinander Zu zunächst die Ausgabe beeinflussen.

Race-Bedingung in einer Logikschaltung:

enter image description here

Die Softwareindustrie hat diesen Begriff unverändert übernommen, was das Verständnis etwas erschwert.

Sie müssen etwas ersetzen, um es der Softwarewelt zuzuordnen:

  • „zwei Signale“ => „zwei Threads“/“zwei Prozesse“
  • „die Ausgabe beeinflussen“ => „einen gemeinsamen Zustand beeinflussen“

Unter Race Condition in der Softwareindustrie versteht man also „zwei Threads“/„zwei Prozesse“, die gegeneinander antreten, um „einen gemeinsamen Zustand zu beeinflussen“, und das Endergebnis des gemeinsamen Zustands hängt von einem subtilen Zeitunterschied ab, der durch bestimmte Faktoren verursacht werden kann Thread-/Prozessstartreihenfolge, Thread-/Prozessplanung usw.

Eine Race-Bedingung ist eine Situation bei der gleichzeitigen Programmierung, in der zwei gleichzeitige Threads oder Prozesse um eine Ressource konkurrieren und der resultierende Endzustand davon abhängt, wer die Ressource zuerst erhält.

Microsoft hat tatsächlich eine sehr detaillierte Version veröffentlicht Artikel zum Thema Rennbedingungen und Deadlocks.Die am besten zusammengefasste Zusammenfassung daraus wäre der Titelabsatz:

Eine Rennbedingung tritt auf, wenn zwei Threads gleichzeitig auf eine gemeinsam genutzte Variable zugreifen.Der erste Thread liest die Variable und der zweite Thread liest den gleichen Wert aus der Variablen.Dann führen der erste Thread und der zweite Thread ihre Vorgänge auf dem Wert aus, und sie sehen, welchen Thread den Wert zuletzt in die gemeinsame Variable schreiben kann.Der Wert des Threads, der seinen letzten Wert schreibt, bleibt erhalten, da der Thread über den Wert schreibt, den der vorherige Thread geschrieben hat.

Was ist eine Rennbedingung?

Die Situation, in der der Prozess entscheidend von der Reihenfolge oder dem Zeitpunkt anderer Ereignisse abhängt.

Zum Beispiel Prozessor A und Prozessor B beide Bedürfnisse identische Ressource für ihre Ausführung.

Wie erkennt man sie?

Es gibt Tools zur automatischen Erkennung von Race Conditions:

Wie gehst du damit um?

Die Rennbedingung kann von behandelt werden Mutex oder Semaphore.Sie fungieren als Sperre, die es einem Prozess ermöglicht, eine Ressource basierend auf bestimmten Anforderungen zu erwerben, um Race Conditions zu verhindern.

Wie verhindern Sie, dass sie auftreten?

Es gibt verschiedene Möglichkeiten, Race Conditions zu verhindern, z Vermeidung kritischer Abschnitte.

  1. Keine zwei Prozesse gleichzeitig in ihren kritischen Bereichen.(Gegenseitiger Ausschluss)
  2. Es werden keine Annahmen über Geschwindigkeiten oder die Anzahl der CPUs gemacht.
  3. Kein Prozess läuft außerhalb seines kritischen Bereichs, der andere Prozesse blockiert.
  4. Kein Prozess muss ewig warten, um in seinen kritischen Bereich zu gelangen.(A wartet auf B-Ressourcen, B wartet auf C-Ressourcen, C wartet auf A-Ressourcen)

Eine Race-Bedingung ist eine unerwünschte Situation, die auftritt, wenn ein Gerät oder System versucht, zwei oder mehr Vorgänge gleichzeitig auszuführen, die Vorgänge jedoch aufgrund der Art des Geräts oder Systems in der richtigen Reihenfolge ausgeführt werden müssen richtig gemacht.

Im Arbeitsspeicher oder Datenspeicher eines Computers kann es zu einer Race-Bedingung kommen, wenn Befehle zum Lesen und Schreiben einer großen Datenmenge nahezu gleichzeitig empfangen werden und die Maschine versucht, einige oder alle alten Daten zu überschreiben, während diese noch vorhanden sind lesen.Das Ergebnis kann eines oder mehrere der folgenden sein:ein Computerabsturz, ein „illegaler Vorgang“, eine Benachrichtigung und das Herunterfahren des Programms, Fehler beim Lesen der alten Daten oder Fehler beim Schreiben der neuen Daten.

Hier ist das klassische Beispiel für den Bankkontostand, das Neulingen dabei helfen wird, Threads in Java leicht zu verstehen.Rennbedingungen:

public class BankAccount {

/**
 * @param args
 */
int accountNumber;
double accountBalance;

public synchronized boolean Deposit(double amount){
    double newAccountBalance=0;
    if(amount<=0){
        return false;
    }
    else {
        newAccountBalance = accountBalance+amount;
        accountBalance=newAccountBalance;
        return true;
    }

}
public synchronized boolean Withdraw(double amount){
    double newAccountBalance=0;
    if(amount>accountBalance){
        return false;
    }
    else{
        newAccountBalance = accountBalance-amount;
        accountBalance=newAccountBalance;
        return true;
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    BankAccount b = new BankAccount();
    b.accountBalance=2000;
    System.out.println(b.Withdraw(3000));

}

Probieren Sie dieses einfache Beispiel aus, um die Rennbedingungen besser zu verstehen:

    public class ThreadRaceCondition {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Account myAccount = new Account(22222222);

        // Expected deposit: 250
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.DEPOSIT, 5.00);
            t.start();
        }

        // Expected withdrawal: 50
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.WITHDRAW, 1.00);
            t.start();

        }

        // Temporary sleep to ensure all threads are completed. Don't use in
        // realworld :-)
        Thread.sleep(1000);
        // Expected account balance is 200
        System.out.println("Final Account Balance: "
                + myAccount.getAccountBalance());

    }

}

class Transaction extends Thread {

    public static enum TransactionType {
        DEPOSIT(1), WITHDRAW(2);

        private int value;

        private TransactionType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    };

    private TransactionType transactionType;
    private Account account;
    private double amount;

    /*
     * If transactionType == 1, deposit else if transactionType == 2 withdraw
     */
    public Transaction(Account account, TransactionType transactionType,
            double amount) {
        this.transactionType = transactionType;
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        switch (this.transactionType) {
        case DEPOSIT:
            deposit();
            printBalance();
            break;
        case WITHDRAW:
            withdraw();
            printBalance();
            break;
        default:
            System.out.println("NOT A VALID TRANSACTION");
        }
        ;
    }

    public void deposit() {
        this.account.deposit(this.amount);
    }

    public void withdraw() {
        this.account.withdraw(amount);
    }

    public void printBalance() {
        System.out.println(Thread.currentThread().getName()
                + " : TransactionType: " + this.transactionType + ", Amount: "
                + this.amount);
        System.out.println("Account Balance: "
                + this.account.getAccountBalance());
    }
}

class Account {
    private int accountNumber;
    private double accountBalance;

    public int getAccountNumber() {
        return accountNumber;
    }

    public double getAccountBalance() {
        return accountBalance;
    }

    public Account(int accountNumber) {
        this.accountNumber = accountNumber;
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean deposit(double amount) {
        if (amount < 0) {
            return false;
        } else {
            accountBalance = accountBalance + amount;
            return true;
        }
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean withdraw(double amount) {
        if (amount > accountBalance) {
            return false;
        } else {
            accountBalance = accountBalance - amount;
            return true;
        }
    }
}

Sie möchten eine Rennbedingung nicht immer verwerfen.Wenn Sie ein Flag haben, das von mehreren Threads gelesen und geschrieben werden kann, und dieses Flag von einem Thread auf „Fertig“ gesetzt wird, sodass ein anderer Thread die Verarbeitung stoppt, wenn das Flag auf „Fertig“ gesetzt ist, möchten Sie dieses „Rennen“ nicht Zustand" beseitigt werden.Tatsächlich kann man von einer harmlosen Rassenerkrankung sprechen.

Wenn jedoch ein Tool zur Erkennung von Race Conditions verwendet wird, wird es als schädliche Race Condition erkannt.

Weitere Details zu den Rennbedingungen finden Sie hier. http://msdn.microsoft.com/en-us/magazine/cc546569.aspx.

Stellen Sie sich eine Operation vor, die den Zählerstand anzeigen muss, sobald der Zählerstand erhöht wird.d.h. sobald CounterThread erhöht den Wert DisplayThread muss den zuletzt aktualisierten Wert anzeigen.

int i = 0;

Ausgabe

CounterThread -> i = 1  
DisplayThread -> i = 1  
CounterThread -> i = 2  
CounterThread -> i = 3  
CounterThread -> i = 4  
DisplayThread -> i = 4

Hier CounterThread Ruft die Sperre regelmäßig ab und aktualisiert den Wert vorher DisplayThread zeigt es an.Hier liegt eine Race-Bedingung vor.Race Condition kann durch Verwendung von Synchronisierung gelöst werden

Du kannst Rennbedingung verhindern, wenn Sie „Atomic“-Klassen verwenden.Der Grund dafür ist einfach, dass der Thread die Operation „get“ und „set“ nicht trennt. Das Beispiel ist unten:

AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);

Als Ergebnis erhalten Sie 7 im Link „ai“.Sie haben zwar zwei Aktionen ausgeführt, aber beide Vorgänge bestätigen denselben Thread und kein anderer Thread wird sich darauf einmischen, das bedeutet, dass keine Race-Bedingungen vorliegen!

Eine Race-Bedingung ist eine unerwünschte Situation, die auftritt, wenn zwei oder mehr Prozesse gleichzeitig auf die gemeinsam genutzten Daten zugreifen und diese ändern können. Sie trat auf, weil es widersprüchliche Zugriffe auf eine Ressource gab.Ein Problem mit einem kritischen Abschnitt kann zu einer Rennbedingung führen.Um den kritischen Zustand im Prozess zu lösen, müssen wir jeweils nur einen Prozess herausnehmen, der den kritischen Abschnitt ausführt.

public class Synchronized_RACECONDITION {
    private static final int NUM_INCREMENTS = 10000;

    private static int count = 0;

    public static void main(String[] args) {
        testSyncIncrement();
        testNonSyncIncrement();
    }

    private static void testSyncIncrement() {
        count = 0;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, NUM_INCREMENTS)
                .forEach(i -> executor.submit(Synchronized_RACECONDITION::incrementSync));

        ConcurrentUtils.stop(executor);

        System.out.println("   Sync: " + count);
    }

    private static void testNonSyncIncrement() {
        count = 0;

        ExecutorService executor = Executors.newFixedThreadPool(2);

        IntStream.range(0, NUM_INCREMENTS)
                .forEach(i -> executor.submit(Synchronized_RACECONDITION::increment));

        ConcurrentUtils.stop(executor);

        System.out.println("NonSync: " + count);
    }

    private static synchronized void incrementSync() {
        count = count + 1;
    }

    private static void increment() {
        count = count + 1;
    }
static  class ConcurrentUtils {

    public static void stop(ExecutorService executor) {
        try {
            executor.shutdown();
            executor.awaitTermination(60, TimeUnit.SECONDS);
        }
        catch (InterruptedException e) {
            System.err.println("termination interrupted");
        }
        finally {
            if (!executor.isTerminated()) {
                System.err.println("killing non-finished tasks");
            }
            executor.shutdownNow();
        }
    }
}
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top