Aufruf von Thread run() statt start()

Beschreibung

Aufruf von Thread run() statt start() ist ein Programmierfehler in Java, bei dem Entwickler fälschlicherweise die run()-Methode direkt auf einem Thread-Objekt aufrufen statt start() aufzurufen. Wenn run() direkt aufgerufen wird, wird der Code synchron im aufrufenden Thread ausgeführt, anstatt einen neuen Thread zu starten. Dies macht den Zweck von Multithreading vollständig zunichte - die Methode wird sequentiell ausgeführt und blockiert den Aufrufer bis zur Fertigstellung. Die beabsichtigte parallele Ausführung findet nie statt, was Leistungsprobleme, Deadlocks oder falsches Verhalten in Anwendungen verursachen kann, die auf paralleler Ausführung angewiesen sind.

Risiko

Der Aufruf von run() statt start() eliminiert die erwartete Parallelität und verursacht schwere Anwendungsprobleme. Operationen, die parallel laufen sollten, werden sequentiell ausgeführt, was Leistungsengpässe und nicht reagierende Anwendungen erzeugt. GUI-Anwendungen können einfrieren, weil blockierende Operationen auf dem Event-Dispatch-Thread laufen. Server-Anwendungen verlieren ihre Fähigkeit, gleichzeitige Anfragen zu verarbeiten. Kritischer ist, dass Code, der parallele Ausführung annimmt, in einen Deadlock geraten kann, wenn er synchron läuft, da Threads, die auf Ergebnisse voneinander warten, tatsächlich derselbe Thread sind. Anwendungen können Leistungsanforderungen nicht erfüllen oder Race-Condition-ähnliche Symptome zeigen, die verschwinden, wenn der Bug behoben wird.

Lösung

Verwenden Sie immer Thread.start(), um die Thread-Ausführung zu beginnen, anstatt run() direkt aufzurufen. Die start()-Methode erstellt einen neuen Ausführungsthread und ruft dann run() innerhalb dieses neuen Thread-Kontexts auf. Verwenden Sie statische Analysewerkzeuge und IDE-Warnungen, um direkte run()-Aufrufe auf Thread-Objekten zu erkennen. Erwägen Sie die Verwendung von höherstufigen Parallelitätsdienstprogrammen wie ExecutorService, die sauberere APIs bieten, die weniger anfällig für diesen Fehler sind. Code-Reviews sollten Thread-Initialisierungsmuster spezifisch prüfen. Beim Testen von parallelem Code verifizieren Sie, dass Operationen tatsächlich parallel ausgeführt werden, mittels Thread-Identifikation oder Timing-Analyse.

Häufige Auswirkungen

AuswirkungDetails
SonstigeBereich: Sonstige

Qualitätsverschlechterung - Die Anwendung erreicht die beabsichtigte Parallelität nicht, was zu sequentieller Ausführung führt, wo parallele Ausführung vorgesehen war.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Ressourcenverbrauch - Sequentielle Ausführung von beabsichtigt parallelen Operationen verursacht Leistungsverschlechterung und macht die Anwendung potenziell nicht reaktionsfähig.

Beispielcode

Verwundbarer Code

// Verwundbar: Direkter Aufruf von run() statt start()
public class VulnerableThreadExample {

    public void processDataConcurrently(List<DataItem> items) {
        for (DataItem item : items) {
            Thread worker = new Thread(new DataProcessor(item));

            // Verwundbar: Direkter run()-Aufruf - führt SYNCHRON aus
            worker.run();  // Falsch! Dies blockiert und läuft im aktuellen Thread

            // Jedes Element wird sequentiell verarbeitet, nicht parallel
            // Erwartete parallele Verarbeitung findet nie statt
        }
    }
}

// Verwundbar: Hintergrundaufgabe die blockiert
public class VulnerableBackgroundTask {

    public void startBackgroundProcess() {
        Thread backgroundThread = new Thread(() -> {
            // Langlaufende Operation
            performExpensiveCalculation();
            updateDatabase();
            sendNotifications();
        });

        // Verwundbar: Aufrufer blockiert bis alle Operationen fertig sind
        backgroundThread.run();  // Falsch!

        // Diese Zeile wird erst erreicht nachdem Hintergrundaufgabe fertig ist
        System.out.println("Hintergrundaufgabe gestartet");  // Irreführende Nachricht
    }
}

// Verwundbar: GUI friert ein wegen run()-Aufruf
public class VulnerableGuiApplication extends JFrame {

    private void loadDataButton_Click() {
        Thread loaderThread = new Thread(() -> {
            // Daten vom Remote-Server holen (langsame Operation)
            List<Record> records = fetchFromServer();
            updateTable(records);
        });

        // Verwundbar: GUI-Thread blockiert, Anwendung friert ein
        loaderThread.run();  // Falsch! Friert UI ein bis Daten geladen

        // Benutzer sieht eingefrorene, nicht reagierende Anwendung
    }
}

// Verwundbar: Deadlock durch synchrone Ausführung
public class VulnerableDeadlockExample {
    private final Object lock = new Object();
    private String result = null;

    public String fetchWithTimeout() {
        Thread fetchThread = new Thread(() -> {
            synchronized (lock) {
                result = performFetch();
                lock.notifyAll();
            }
        });

        // Verwundbar: Dies erzeugt einen Deadlock!
        synchronized (lock) {
            fetchThread.run();  // Falsch! Läuft im AKTUELLEN Thread
            // run() versucht Lock zu erwerben, den dieser Thread bereits hält
            // Mit start() würde anderer Thread auf Lock warten

            try {
                lock.wait(5000);  // Auf Ergebnis warten
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        return result;
    }
}

// Verwundbar: Thread-Pool-Simulation schlägt fehl
public class VulnerableWorkerPool {
    private List<Thread> workers = new ArrayList<>();

    public void submitTasks(List<Runnable> tasks) {
        for (Runnable task : tasks) {
            Thread worker = new Thread(task);
            workers.add(worker);
        }

        // Verwundbar: Tasks laufen sequentiell, nicht parallel
        for (Thread worker : workers) {
            worker.run();  // Falsch! Jede Task blockiert bis fertig
        }

        // Leistung ist nicht besser als Single-Threaded-Ausführung
    }
}
// Verwundbar: Unterklasse ruft super.run() auf
public class VulnerableCustomThread extends Thread {

    @Override
    public void run() {
        System.out.println("Benutzerdefinierte Verarbeitung");
        // ... Arbeit erledigen
    }

    public void execute() {
        // Verwundbar: Sollte start() aufrufen, nicht run()
        this.run();  // Falsch! Synchrone Ausführung
    }
}

// Verwundbar: Anonyme innere Klasse
public class VulnerableAnonymousThread {

    public void process() {
        // Verwundbar: Direktes run() auf anonymem Thread
        new Thread() {
            @Override
            public void run() {
                expensiveOperation();
            }
        }.run();  // Falsch! Sollte .start() sein
    }
}

Lösungscode

// Behoben: Verwendung von start() für ordnungsgemäße parallele Ausführung
public class SecureThreadExample {

    public void processDataConcurrently(List<DataItem> items) {
        List<Thread> threads = new ArrayList<>();

        for (DataItem item : items) {
            Thread worker = new Thread(new DataProcessor(item));
            threads.add(worker);

            // Behoben: start() erstellt neuen Thread und ruft run() darin auf
            worker.start();  // Korrekt! Läuft parallel
        }

        // Auf alle Threads warten bis fertig
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// Behoben: Nicht-blockierende Hintergrundaufgabe
public class SecureBackgroundTask {

    public void startBackgroundProcess() {
        Thread backgroundThread = new Thread(() -> {
            performExpensiveCalculation();
            updateDatabase();
            sendNotifications();
        });

        // Behoben: Aufrufer fährt sofort fort
        backgroundThread.start();  // Korrekt!

        // Dies wird sofort ausgeführt während Hintergrundaufgabe läuft
        System.out.println("Hintergrundaufgabe gestartet");
    }
}

// Behoben: Reaktionsfähige GUI mit ordnungsgemäßem Threading
public class SecureGuiApplication extends JFrame {

    private void loadDataButton_Click() {
        // Schaltfläche deaktivieren um Doppelklicks zu verhindern
        loadButton.setEnabled(false);

        Thread loaderThread = new Thread(() -> {
            List<Record> records = fetchFromServer();

            // GUI auf Event-Dispatch-Thread aktualisieren
            SwingUtilities.invokeLater(() -> {
                updateTable(records);
                loadButton.setEnabled(true);
            });
        });

        // Behoben: GUI bleibt reaktionsfähig
        loaderThread.start();  // Korrekt! UI-Thread fährt fort
    }
}

// Behoben: Kein Deadlock mit ordnungsgemäßer Thread-Erstellung
public class SecureNoDeadlockExample {
    private final Object lock = new Object();
    private volatile String result = null;

    public String fetchWithTimeout() {
        Thread fetchThread = new Thread(() -> {
            String fetchedResult = performFetch();
            synchronized (lock) {
                result = fetchedResult;
                lock.notifyAll();
            }
        });

        synchronized (lock) {
            // Behoben: start() erstellt separaten Thread der Lock später erwerben kann
            fetchThread.start();  // Korrekt!

            try {
                lock.wait(5000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        return result;
    }
}
// Besser: ExecutorService für Thread-Verwaltung verwenden
import java.util.concurrent.*;

public class ModernConcurrencyExample {

    private final ExecutorService executor = Executors.newFixedThreadPool(4);

    public void processDataConcurrently(List<DataItem> items) {
        List<Future<?>> futures = new ArrayList<>();

        for (DataItem item : items) {
            // Besser: ExecutorService verwaltet Thread-Lebenszyklus
            Future<?> future = executor.submit(() -> {
                new DataProcessor(item).process();
            });
            futures.add(future);
        }

        // Auf Fertigstellung warten
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                handleError(e);
            }
        }
    }

    public void shutdown() {
        executor.shutdown();
    }
}

// Besser: CompletableFuture für asynchrone Operationen
public class AsyncExample {

    public CompletableFuture<List<Record>> loadDataAsync() {
        return CompletableFuture.supplyAsync(() -> {
            return fetchFromServer();
        });
    }

    public void example() {
        loadDataAsync()
            .thenAccept(records -> {
                // Bei Fertigstellung verarbeiten
                updateTable(records);
            })
            .exceptionally(ex -> {
                // Fehler behandeln
                showError(ex);
                return null;
            });

        // Aufrufer fährt sofort fort
        System.out.println("Laden gestartet...");
    }
}

// Behoben: Benutzerdefinierte Thread-Klasse mit ordnungsgemäßer Ausführung
public class SecureCustomThread extends Thread {

    @Override
    public void run() {
        System.out.println("Benutzerdefinierte Verarbeitung in Thread: " +
            Thread.currentThread().getName());
    }

    public void execute() {
        // Behoben: start() für parallele Ausführung aufrufen
        this.start();  // Korrekt!
    }

    // Besser: Versehentliche run()-Aufrufe verhindern
    public static void executeTask(Runnable task) {
        Thread thread = new Thread(task);
        thread.start();  // Immer start() verwenden
    }
}

// Parallele Streams als Alternative
public class ParallelStreamExample {

    public void processDataParallel(List<DataItem> items) {
        // Alternative: Parallele Streams für einfache parallele Operationen
        items.parallelStream()
            .forEach(item -> new DataProcessor(item).process());
    }
}

CVE-Beispiele

Keine spezifischen CVEs werden dieser CWE üblicherweise zugeordnet. Das Bug-Muster ist jedoch in Java-Programmierressourcen und statischen Analysewerkzeugen gut dokumentiert.


Referenzen

  1. MITRE Corporation. "CWE-572: Call to Thread run() Instead of start()." https://cwe.mitre.org/data/definitions/572.html
  2. Oracle. "Java Thread Documentation."
  3. FindBugs. "RU: Invocation of run on a Thread (RU_INVOKE_RUN)."