Pourquoi ne pas démarrer un fil de discussion dans le constructeur?

1. Vue d'ensemble

Dans ce rapide tutoriel, nous allons voir pourquoi nous ne devrions pas démarrer un thread à l'intérieur d'un constructeur.

Tout d'abord, nous présenterons brièvement le concept de publication en Java et JVM. Ensuite, nous verrons comment ce concept affecte la façon dont nous démarrons les threads.

2. Publication et évasion

Chaque fois que nous mettons un objet à la disposition de tout autre code en dehors de sa portée actuelle, nous publions essentiellement cet objet . Par exemple, la publication se produit lorsque nous renvoyons un objet, le stockons dans une référence publique ou même le passons à une autre méthode.

Lorsque nous publions un objet que nous ne devrions pas avoir, nous disons que l'objet s'est échappé .

Il existe de nombreuses façons de laisser une référence d'objet s'échapper, comme la publication de l'objet avant sa construction complète. En fait, c'est l'une des formes courantes de fuite: lorsque cette référence s'échappe pendant la construction de l'objet.

Lorsque la référence this s'échappe pendant la construction, d'autres threads peuvent voir cet objet dans un état incorrect et pas entièrement construit. Ceci, à son tour, peut entraîner d'étranges complications liées à la sécurité des fils.

3. Échappement avec des threads

L'un des moyens les plus courants de laisser cette référence s'échapper est de démarrer un thread dans un constructeur. Pour mieux comprendre cela, prenons un exemple:

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

Ici, nous transmettons explicitement la référence this au constructeur Thread . Par conséquent, le thread nouvellement démarré peut voir l'objet englobant avant que sa construction complète ne soit terminée. Dans des contextes simultanés, cela peut provoquer des bogues subtils.

Il est également possible de passer implicitement cette référence :

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

Comme indiqué ci-dessus, nous créons une classe interne anonyme dérivée de Thread . Comme les classes internes conservent une référence à leur classe englobante, la référence this s'échappe à nouveau du constructeur.

Il n'y a rien de mal en soi à créer un Thread dans un constructeur. Cependant, il est fortement déconseillé de commencer immédiatement , comme la plupart du temps, nous nous retrouvons avec un échappé cette référence, que ce soit explicitement ou implicitement.

3.1. Alternatives

Au lieu de démarrer un thread dans un constructeur, nous pouvons déclarer une méthode dédiée pour ce scénario:

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(); } };:

Comme indiqué ci-dessus, nous publions toujours la référence this au Thread. Cependant, cette fois, nous démarrons le thread après le retour du constructeur:

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

Par conséquent, la référence d'objet n'échappe pas à un autre thread avant sa construction complète.

4. Conclusion

Dans ce tutoriel rapide, après une brève introduction à la publication sécurisée, nous avons vu pourquoi nous ne devrions pas démarrer un thread à l'intérieur d'un constructeur.

Des informations plus détaillées sur la publication et l'évasion en Java peuvent être trouvées dans le livre Java Concurrency in Practice.

Comme d'habitude, tous les exemples sont disponibles sur over sur GitHub.