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
| Auswirkung | Details |
|---|---|
| Sonstige | Bereich: 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ügbarkeit | Bereich: 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
- MITRE Corporation. "CWE-572: Call to Thread run() Instead of start()." https://cwe.mitre.org/data/definitions/572.html
- Oracle. "Java Thread Documentation."
- FindBugs. "RU: Invocation of run on a Thread (RU_INVOKE_RUN)."