Warum nicht einen Thread im Konstruktor starten?

1. Übersicht

In diesem kurzen Tutorial werden wir sehen, warum wir keinen Thread in einem Konstruktor starten sollten.

Zunächst stellen wir kurz das Publikationskonzept in Java und JVM vor. Dann werden wir sehen, wie sich dieses Konzept auf die Art und Weise auswirkt, wie wir Threads starten.

2. Veröffentlichung und Flucht

Jedes Mal, wenn wir ein Objekt für einen anderen Code außerhalb seines aktuellen Bereichs verfügbar machen, veröffentlichen wir dieses Objekt grundsätzlich . Zum Beispiel erfolgt die Veröffentlichung, wenn wir ein Objekt zurückgeben, es in einer öffentlichen Referenz speichern oder es sogar an eine andere Methode übergeben.

Wenn wir ein Objekt veröffentlichen, das wir nicht haben sollten, sagen wir, dass das Objekt entkommen ist .

Es gibt viele Möglichkeiten, wie wir eine Objektreferenz entkommen lassen können, z. B. das Veröffentlichen des Objekts vor seiner vollständigen Konstruktion. Tatsächlich ist dies eine der häufigsten Fluchtformen: Wenn diese Referenz während der Objektkonstruktion entkommt.

Wenn diese Referenz während der Erstellung ausgeblendet wird, sehen andere Threads dieses Objekt möglicherweise in einem nicht ordnungsgemäßen und nicht vollständig erstellten Zustand. Dies kann wiederum seltsame Komplikationen bei der Gewindesicherheit verursachen.

3. Mit Fäden entkommen

Eine der häufigsten Möglichkeiten, diese Referenz entkommen zu lassen, besteht darin, einen Thread in einem Konstruktor zu starten. Um dies besser zu verstehen, betrachten wir ein Beispiel:

public class LoggerRunnable implements Runnable { public LoggerRunnable() { Thread thread = new Thread(this); // this escapes thread.start(); } @Override public void run() { System.out.println("Started..."); } }

Hier übergeben wir diesen Verweis explizit an den Thread- Konstruktor. Daher kann der neu gestartete Thread das einschließende Objekt möglicherweise sehen, bevor seine vollständige Konstruktion abgeschlossen ist. In gleichzeitigen Kontexten kann dies zu subtilen Fehlern führen.

Es ist auch möglich, diese Referenz implizit zu übergeben :

public class ImplicitEscape { public ImplicitEscape() { Thread t = new Thread() { @Override public void run() { System.out.println("Started..."); } }; t.start(); } }

Wie oben gezeigt, erstellen wir eine anonyme innere Klasse, die vom Thread abgeleitet ist . Da innere Klassen einen Verweis auf ihre einschließende Klasse beibehalten, entweicht dieser Verweis erneut dem Konstruktor.

Es ist an sich nichts Falsches daran, einen Thread in einem Konstruktor zu erstellen . Es wird jedoch dringend davon abgeraten, es sofort zu starten , da wir die meiste Zeit explizit oder implizit dieser Referenz entkommen sind .

3.1. Alternativen

Anstatt einen Thread in einem Konstruktor zu starten, können wir eine dedizierte Methode für dieses Szenario deklarieren:

public class SafePublication implements Runnable { private final Thread thread; public SafePublication() { thread = new Thread(this); } @Override public void run() { System.out.println("Started..."); } public void start() { thread.start(); } };:

Wie oben gezeigt, veröffentlichen wir diesen Verweis weiterhin auf den Thread. Dieses Mal starten wir jedoch den Thread, nachdem der Konstruktor zurückgegeben hat:

SafePublication publication = new SafePublication(); publication.start();

Daher wird die Objektreferenz vor ihrer vollständigen Konstruktion nicht in einen anderen Thread übertragen.

4. Fazit

In diesem kurzen Tutorial haben wir nach einer kurzen Einführung in die sichere Veröffentlichung gesehen, warum wir keinen Thread in einem Konstruktor starten sollten.

Weitere Informationen zur Veröffentlichung und zum Escape in Java finden Sie im Buch Java Concurrency in Practice.

Wie üblich sind alle Beispiele auf GitHub verfügbar.