Ceci est un sondage de toutes sortes sur les problèmes de simultanéité courants en Java. Un exemple pourrait être l’impasse classique ou l’état de concurrence, ou peut-être un problème de threading EDT dans Swing. Je m'intéresse à la fois à un éventail de problèmes possibles mais aussi aux problèmes les plus courants. Donc, s'il vous plaît laissez une réponse spécifique d'un Java bug d'accès par commentaire et par commentaire et votez si vous en voyez un que vous avez rencontré.
Le problème de concurrence le plus courant que j'ai vu, consiste à ne pas réaliser qu'un champ écrit par un thread est non garanti visible par un autre thread. Une application commune de ceci:
class MyThread extends Thread {
private boolean stop = false;
public void run() {
while(!stop) {
doSomeWork();
}
}
public void setStop() {
this.stop = true;
}
}
Tant que stop n'est pas volatile ou setStop
et run
ne sont pas synchronisé cela ne garantit pas son fonctionnement. Cette erreur est particulièrement diabolique: dans 99,999% des cas, cela n’aura pas d’importance dans la pratique, car le fil de discussion qui verra le lecteur finira par voir le changement - mais nous ne savons pas quand il le verra vite.
Mon # 1 plus douloureux problème de simultanéité s'est déjà produit lorsque deux bibliothèques différentes ont ouvert quelque chose comme ceci:
private static final String LOCK = "LOCK"; // use matching strings
// in two different libraries
public doSomestuff() {
synchronized(LOCK) {
this.work();
}
}
À première vue, cela ressemble à un exemple de synchronisation assez trivial. Toutefois; parce que les chaînes sont internées en Java, la chaîne littérale "LOCK"
s'avère être la même instance de Java.lang.String
(même s’ils sont déclarés complètement disparates les uns des autres.) Le résultat est évidemment mauvais.
Un problème classique consiste à changer l'objet sur lequel vous synchronisez pendant la synchronisation:
synchronized(foo) {
foo = ...
}
D'autres threads simultanés se synchronisent ensuite sur un objet différent et ce bloc ne fournit pas l'exclusion mutuelle attendue.
Un problème courant consiste à utiliser des classes telles que Calendar et SimpleDateFormat à partir de plusieurs threads (souvent en les mettant en cache dans une variable statique) sans synchronisation. Ces classes ne sont pas sécurisées pour les threads. Par conséquent, l'accès multithread entraînera des problèmes étranges avec un état incohérent.
Verrouillage à double vérification. Dans l'ensemble.
Le paradigme, sur lequel j'ai commencé à apprendre les problèmes de quand je travaillais chez BEA, est que les gens vont vérifier un singleton de la manière suivante:
public Class MySingleton {
private static MySingleton s_instance;
public static MySingleton getInstance() {
if(s_instance == null) {
synchronized(MySingleton.class) { s_instance = new MySingleton(); }
}
return s_instance;
}
}
Cela ne fonctionne jamais, car un autre thread pourrait être entré dans le bloc synchronisé et que s_instance n'est plus nul. Le changement naturel consiste donc à le faire:
public static MySingleton getInstance() {
if(s_instance == null) {
synchronized(MySingleton.class) {
if(s_instance == null) s_instance = new MySingleton();
}
}
return s_instance;
}
Cela ne fonctionne pas non plus, car le modèle de mémoire Java ne le prend pas en charge. Vous devez déclarer s_instance comme étant volatile pour que cela fonctionne. Même dans ce cas, il ne fonctionne que Java 5.
Les gens qui ne sont pas familiers avec les subtilités du Java Le modèle de mémoire bousille tout cela tout le temps.
Pas correctement synchronisation sur les objets retournés par Collections.synchronizedXXX()
, en particulier lors d'une itération ou de plusieurs opérations:
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
...
if(!map.containsKey("foo"))
map.put("foo", "bar");
C'est faux. Bien que les opérations simples soient synchronized
, l'état de la carte entre l'appel de contains
et put
peut être modifié par un autre thread. CA devrait etre:
synchronized(map) {
if(!map.containsKey("foo"))
map.put("foo", "bar");
}
Ou avec une implémentation ConcurrentMap
:
map.putIfAbsent("foo", "bar");
Bien que ce ne soit probablement pas exactement ce que vous demandez, le problème le plus fréquent que j’ai rencontré concernant la concurrence (probablement parce qu’il survient dans du code normal à un seul thread) est un problème récurrent.
Java.util.ConcurrentModificationException
causé par des choses comme:
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }
Le bogue le plus courant où nous travaillons concerne les programmeurs qui effectuent de longues opérations, telles que des appels de serveur, sur l'EDT, bloquant l'interface graphique pendant quelques secondes et rendant l'application inactive.
Il peut être facile de penser que les collections synchronisées vous accordent plus de protection qu’elles ne le font réellement, et d’oublier de garder le verrou entre les appels. J'ai vu cette erreur plusieurs fois:
List<String> l = Collections.synchronizedList(new ArrayList<String>());
String[] s = l.toArray(new String[l.size()]);
Par exemple, dans la deuxième ligne ci-dessus, les méthodes toArray()
et size()
sont toutes deux thread-safe, mais la size()
est évaluée séparément de la méthode toArray()
, et le verrou sur la liste n'est pas maintenu entre ces deux appels.
Si vous exécutez ce code avec un autre thread simultanément en supprimant des éléments de la liste, vous obtiendrez tôt ou tard un nouveau String[]
Renvoyé, qui est plus volumineux que nécessaire pour contenir tous les éléments la liste, et a des valeurs nulles dans la queue. Il est facile de penser que, comme les deux appels de méthode à la liste se produisent dans une seule ligne de code, il s’agit d’une opération atomique, mais ce n’est pas le cas.
Oublier d'attendre () (ou Condition.await ()) dans une boucle, en vérifiant que la condition d'attente est bien vraie. Sans cela, vous rencontrez des bugs causés par de faux wakeups. L'utilisation canonique devrait être:
synchronized (obj) {
while (<condition does not hold>) {
obj.wait();
}
// do stuff based on condition being true
}
Un autre bug courant est la mauvaise gestion des exceptions. Lorsqu'un thread d'arrière-plan lève une exception, si vous ne le gérez pas correctement, vous risquez de ne pas voir la trace de pile. Ou peut-être que votre tâche d’arrière-plan cesse de fonctionner et ne redémarre jamais car vous n’avez pas réussi à gérer l’exception.
Avant de suivre un cours avec Brian Goetz, je ne savais pas que le getter
non synchronisé d'un champ privé muté via un setter
synchronisé n'était jamais garanti de retourner la valeur mise à jour. Ce n'est que lorsqu'une variable est protégée par un bloc synchronisé sur à la fois, AND lit et écrit que vous aurez la garantie de la dernière valeur de la variable.
public class SomeClass{
private Integer thing = 1;
public synchronized void setThing(Integer thing)
this.thing = thing;
}
/**
* This may return 1 forever and ever no matter what is set
* because the read is not synched
*/
public Integer getThing(){
return thing;
}
}
Penser que vous écrivez du code à un seul thread, mais en utilisant une statique mutable (y compris des singletons). De toute évidence, ils seront partagés entre les threads. Cela arrive étonnamment souvent.
Les appels de méthode arbitraires ne doivent pas être effectués à l'intérieur de blocs synchronisés.
Dave Ray en a parlé dans sa première réponse. En fait, j'ai également rencontré une impasse liée à l'invocation de méthodes sur des écouteurs à partir d'une méthode synchronisée. Je pense que la leçon la plus générale est que les appels de méthode ne doivent pas être effectués "dans la nature" à l'intérieur d'un bloc synchronisé - vous ne savez pas si l'appel sera long, avec pour résultat un blocage ou autre.
Dans ce cas, et généralement en général, la solution consistait à réduire la portée du bloc synchronisé pour simplement protéger une section de code critique privée .
De plus, étant donné que nous accédions maintenant à la Collection d'écouteurs en dehors d'un bloc synchronisé, nous l'avons changé pour une Collection avec copie sur écriture. Ou nous aurions pu simplement faire une copie défensive de la collection. Le fait est qu'il existe généralement des alternatives pour accéder en toute sécurité à une collection d'objets inconnus.
Le bogue le plus récent lié à la concurrence que j'ai rencontré est un objet qui, dans son constructeur, a créé un ExecutorService, mais lorsque l'objet n'était plus référencé, il n'avait jamais arrêté le service ExecutorService. Ainsi, sur une période de plusieurs semaines, en milliers de threads ont fui, ce qui a fini par provoquer le blocage du système. (Techniquement, il ne s'est pas écrasé, mais il a cessé de fonctionner correctement tout en continuant de fonctionner.)
Techniquement, je suppose que ce n'est pas un problème de simultanéité, mais un problème lié à l'utilisation des bibliothèques Java.util.concurrency.
La synchronisation déséquilibrée, en particulier avec Google Maps, semble être un problème assez courant. Beaucoup de gens croient que la synchronisation sur met sur une carte (pas une ConcurrentMap, mais disons une HashMap) et que la synchronisation sur ne suffit pas. Cela peut toutefois conduire à une boucle infinie lors du re-hachage.
Le même problème (synchronisation partielle) peut se produire partout où vous avez partagé l'état avec les lectures et les écritures.
J'ai rencontré un problème de simultanéité avec les servlets, lorsqu'il y a des champs mutables qui seront définis par chaque requête. Mais comme il n'y a qu'une seule instance de servlet pour toutes les demandes, cela a parfaitement fonctionné dans un environnement utilisateur unique, mais lorsque plusieurs utilisateurs ont demandé à la servlet, des résultats imprévisibles se sont produits.
public class MyServlet implements Servlet{
private Object something;
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException{
this.something = request.getAttribute("something");
doSomething();
}
private void doSomething(){
this.something ...
}
}
Pas vraiment un bug, mais le pire des péchés est de fournir une bibliothèque que d’autres personnes voudraient utiliser, mais sans préciser quelles classes/méthodes sont thread-safe et lesquelles ne doivent être appelées qu’à partir d’un seul thread, etc.
Davantage de personnes devraient utiliser les annotations de concurrence (par exemple, @ThreadSafe, @GuardedBy, etc.) décrites dans le livre de Goetz.
Démarrer un thread dans le constructeur d'une classe est problématique. Si la classe est étendue, le thread peut être démarré avant le constructeur de la sous-classe est exécuté.
Mon plus gros problème a toujours été les blocages, en particulier causés par des auditeurs qui tirent avec un verrou maintenu. Dans ces cas, il est très facile d’obtenir un verrouillage inversé entre deux threads. Dans mon cas, entre une simulation s'exécutant dans un thread et une visualisation de la simulation s'exécutant dans le thread d'interface utilisateur.
EDIT: deuxième partie déplacée pour séparer la réponse.
Classes mutables dans des structures de données partagées
Thread1:
Person p = new Person("John");
sharedMap.put("Key", p);
assert(p.getName().equals("John"); // sometimes passes, sometimes fails
Thread2:
Person p = sharedMap.get("Key");
p.setName("Alfonso");
Lorsque cela se produit, le code est beaucoup plus complexe que cet exemple simplifié. Répliquer, rechercher et corriger le bogue est difficile. Cela pourrait peut-être être évité si nous pouvions marquer certaines classes comme immuables et certaines structures de données comme ne détenant que des objets immuables.
Je crois qu'à l'avenir, le principal problème de Java sera la garantie de visibilité (insuffisante) des constructeurs. Par exemple, si vous créez la classe suivante
class MyClass {
public int a = 1;
}
et puis il suffit de lire la propriété MyClass a à partir d'un autre thread, MyClass.a peut être 0 ou 1, en fonction de l'implémentation et de l'ambiance de JavaVM. Aujourd'hui, les chances pour 'a' d'être 1 sont très élevées. Mais sur les futures machines NUMA, cela peut être différent. Beaucoup de gens n'en sont pas conscients et pensent qu'ils n'ont pas besoin de se préoccuper du multi-threading pendant la phase d'initialisation.
La synchronisation sur un littéral de chaîne ou une constante défini par un littéral de chaîne est (potentiellement) un problème car le littéral de chaîne est interné et sera partagé par quiconque dans la JVM utilisant le même littéral de chaîne. Je sais que ce problème est apparu dans les serveurs d'applications et autres scénarios de "conteneur".
Exemple:
private static final String SOMETHING = "foo";
synchronized(SOMETHING) {
//
}
Dans ce cas, toute personne utilisant la chaîne "foo" pour se verrouiller partage le même verrou.
Un autre problème courant en matière de "simultanéité" consiste à utiliser du code synchronisé lorsque ce n'est pas du tout nécessaire. Par exemple, je vois encore des programmeurs utiliser StringBuffer
ou même Java.util.Vector
(comme variables de méthode locales).
L'erreur la plus stupide que je commets souvent est d'oublier de synchroniser avant d'appeler notify () ou wait () sur un objet.
Utiliser un "nouvel objet ()" local comme mutex.
synchronized (new Object())
{
System.out.println("sdfs");
}
C'est inutile.
Plusieurs objets protégés par un verrou, mais auxquels on accède généralement successivement. Nous avons rencontré quelques cas où les verrous sont obtenus par différents codes dans différents ordres, ce qui aboutit à une impasse.
Ne réalisant pas que le this
d'une classe interne n'est pas le this
de la classe externe. Généralement, dans une classe interne anonyme qui implémente Runnable
. Le problème fondamental est que, comme la synchronisation fait partie de tous les Object
s, il n’existe en réalité aucune vérification de type statique. J'ai vu cela au moins deux fois sur usenet, et il apparaît également dans Brian Goetz'z Java La concurrence dans la pratique.
Les fermetures BGGA n'en souffrent pas car il n'y a pas this
pour la fermeture (this
fait référence à la classe externe). Si vous utilisez des objets non -this
comme verrous, il résout ce problème, entre autres.
Ne réalisant pas le Java.awt.EventQueue.invokeAndWait
agit comme s'il était verrouillé (accès exclusif au fil de distribution des événements, EDT). La grande chose à propos des blocages est que même si cela se produit rarement, vous pouvez récupérer une trace de pile avec jstack ou similaire. Je l'ai vu dans un certain nombre de programmes largement utilisés (une solution à un problème que je n'ai vu se produire qu'une fois dans Netbeans devrait être incluse dans la prochaine version).
1) Une erreur courante que j'ai rencontrée consiste à effectuer une itération sur une classe Collection synchronisée. Il est nécessaire de synchroniser manuellement avant d'obtenir l'itérateur et pendant l'itération.
2) Une autre erreur est que la plupart des manuels donnent l'impression que pour sécuriser un fil de classe, il suffit d'ajouter synchronisé sur chaque méthode. Ce n'est pas en soi une garantie - cela ne protégera que l'intégrité de la classe particulière, mais les résultats peuvent toujours être indéterministes.
3) Le fait de placer trop d’opérations coûteuses en temps dans un bloc synchronisé entraîne souvent de très mauvaises performances. Heureusement, le modèle Future du package de simultanéité peut protéger la journée.
4) La mise en cache d'objets mutables pour améliorer les performances entraîne souvent des problèmes de multithreading (et parfois très difficile à suivre, car vous supposez que vous êtes le seul utilisateur).
5) L'utilisation de plusieurs objets de synchronisation doit être manipulée avec soin.
Essayez ce code ..
public class MyServlet implements Servlet{
private Object something;
public void service(ServletRequest request, ServletResponse response)
throws ServletException, IOException{
this.something = request.getAttribute("something");
doSomething();
}
private void doSomething(){
this.something ...
}
}
Utilisation d'un objet global tel qu'une variable statique pour le verrouillage.
Cela conduit à de très mauvaises performances en raison de conflits.
Honely? Avant l'avènement de Java.util.concurrent
, le problème le plus courant que j’ai rencontré régulièrement était ce que j’appelle "thread-thrashing": les applications qui utilisent des threads pour la simultanéité, mais en génèrent trop et finissent par se débattre.
À partir de Java RMI provoque l'exécution d'une tâche en arrière-plan qui oblige le ramasse-miettes à s'exécuter toutes les 60 secondes. En soi, cela peut être une bonne chose, même si le serveur RMI n'a peut-être pas été activé. commencé par vous directement, mais par un framework/outil que vous utilisez (par exemple, JRun). Et, le RMI peut ne pas être utilisé pour quoi que ce soit.
Le résultat net est un appel System.gc () une fois par minute. Sur un système très chargé, vous verrez la sortie suivante dans vos journaux: 60 secondes d'activité suivies d'une longue pause de type gc suivie de 60 secondes d'activité suivies d'une longue pause de type gc. Ceci est fatal au débit.
La solution consiste à désactiver le mode gc explicite à l'aide de l'option -XX: + DisableExplicitGC
Échec de la définition de méthodes de cycle de vie clairement définies sur des objets gérant des threads de longue durée. J'aime créer des paires de méthodes nommées init () et destroy (). Il est également important d'appeler destroy () pour que votre application puisse se fermer correctement.
Une méthode sauvegardant des données dans une variable d'instance afin de "gagner du temps" en les passant aux méthodes d'assistance, lorsqu'une autre méthode pouvant être appelée simultanément utilise les mêmes variables d'instance à ses propres fins.
Les données doivent plutôt être transmises en tant que paramètres de méthode pour la durée de l'appel synchronisé. Ce n’est qu’une légère simplification de mon pire souvenir:
public class UserService {
private String userName;
public String getUserName() {
return userName;
}
public void login(String name) {
this.userName = name;
doLogin();
}
private void doLogin() {
userDao.login(getUserName());
}
public void delete(String name) {
this.userName = name;
doDelete();
}
private void doDelete() {
userDao.delete(getUserName());
}
}
Les méthodes de connexion et de déconnexion ne doivent pas nécessairement être synchronisées, logiquement. Mais écrit tel quel, vous expérimentez toutes sortes d'appels amusants au service clientèle.
Problème de concurrence lié à l'utilisation de différents objets de verrouillage avec wait et notify.
J'essayais d'utiliser les méthodes wait () et notifyAll () et voici comment j'ai utilisé et tombé en enfer.
Fil 1
Object o1 = new Object();
synchronized(o1) {
o1.wait();
}
Et dans un autre fil. Fil - 2
Object o2 = new Object();
synchronized(o2) {
o2.notifyAll();
}
Thread1 attendra sur o1 et Thread2, qui aurait dû invoquer o1.notifyAll (), appelle o2.notifyAll (). Le fil 1 ne se réveillera jamais.
Et bien sûr, le problème commun qui consiste à ne pas appeler wait () ou notifyAll () dans des blocs synchronisés et à ne pas les appeler à l'aide du même objet que celui utilisé pour synchroniser le bloc.
Object o2 = new Object();
synchronized(o2) {
notifyAll();
}
Cela provoquera IllegalMonitorStateException, puisque le thread qui a appelé notifyAll () a appelé notifyAll () à l'aide de cet objet mais n'est pas le propriétaire de cet objet verrou. Mais le thread actuel est le propriétaire de l'objet de verrouillage o2.
Garder toutes les discussions occupées.
Cela se produit le plus souvent lorsque l'on doit résoudre des problèmes dans le code d'autres personnes, car elles ont abusé des constructions de verrouillage. Dernièrement, mes collègues semblent avoir trouvé les verrous de lecteur/écrivain assez amusants à saupoudrer, alors qu’une petite pensée leur enlève totalement leur besoin.
Dans mon propre code, garder les threads occupés est moins évident, mais difficile. Cela nécessite une réflexion plus approfondie dans les algorithmes, tels que l'écriture de nouvelles structures de données, ou la conception minutieuse d'un système afin de garantir que, lors de l'utilisation du verrouillage, il ne sera jamais contesté.
Il est facile de résoudre les erreurs de concurrence - il peut être difficile de trouver un moyen d'éviter les conflits de verrous.
Conditions de course lors de la méthode de finalisation/libération/arrêt/destruction d'un destructeur et d'appels normaux.
Depuis Java, je réalise beaucoup d’intégration avec des ressources à fermer, telles que des objets COM ou des lecteurs Flash. Les développeurs oublient toujours de le faire correctement et finissent par avoir un thread qui appelle un objet qui a été arrêté.
Depuis Java 5, il existe Thread.getUncaughtExceptionHandler mais ce UncaughtExceptionHandler n’est jamais appelé lorsqu’un ExecutorService/ThreadPool est utilisé.
Au moins, je n’ai pas pu faire fonctionner le UncaughtExceptionHandler avec un ExcutorService.
Mise à jour d'un composant d'interface utilisateur Swing (généralement une barre de progression) dans un thread de travail plutôt que dans le thread Swing (vous devez bien sûr utiliser SwingUtilities.invokeLater(Runnable)
), mais si vous oubliez de le faire, le bogue peut prendre beaucoup de temps faire surface.)
Je suis tombé sur un pseudo-interblocage d'un thread d'E/S qui a créé un verrou de compte à rebours. Une version très simplifiée du problème ressemble à ceci:
public class MyReader implémente Runnable { final final CountDownLatch done = new CountDownLatch (1); privé volatile isOkToRun = true; public void run () { tant que (isOkToRun) { sendMessage (getMessaage ()); } done.countDown (); } public void stop () { isOkToRun = false; done.await (); } }
L'idée de stop () est qu'il ne soit pas retourné jusqu'à ce que le thread se soit terminé. Ainsi, quand il est retourné, le système était dans un état connu. C'est OK, à moins que sendMessage () ne provoque l'invocation de stop (), où il attendra pour toujours. Tant que stop () n'est jamais appelé à partir du Runnable, tout fonctionnera comme prévu. Cependant, dans une grande application, l'activité du fil de discussion de Runnable peut ne pas être évidente!
La solution consistait à appeler wait () avec un délai d'expiration de quelques secondes et à consigner un vidage de pile et à se plaindre à tout moment. Cela préservait le comportement souhaité lorsque cela était possible et exposait les problèmes de codage au fur et à mesure qu'ils se présentaient.
J'essaie d'éviter les problèmes de synchronisation dès le début - méfiez-vous des problèmes/odeurs suivants:
new Thread()
sont une odeur. Utilisez plutôt ExecutorServices, ce qui vous oblige à réfléchir au concept de threading global de votre application (voir 1) et encourage les autres utilisateurs à le suivre.AtomicBoolean
et al , Collections synchronisées, etc.). Encore une fois: prenez une décision consciente quant à l’importance de la sécurité du fil dans un contexte donné, ne les utilisez pas simplement à l’aveuglette.Le plus gros problème que j'ai rencontré est celui des développeurs qui ajoutent un support multi-threading après coup.
public class ThreadA implements Runnable {
private volatile SharedObject obj;
public void run() {
while (true) {
obj = new SharedObject();
obj.setValue("Hallo");
}
}
public SharedObject getObj() {
return obj;
}
}
Le problème que j'essaie de souligner ici (entre autres) est que le vidage de l'objet obj SharedObject se produit avant de définir la valeur "Hallo". Cela signifie que le consommateur de getObj () peut extraire une instance où getValue () renvoie null.
public class ThreadB implements Runnable {
ThreadA a = null;
public ThreadB(ThreadA a) {
this.a = a;
}
public void run() {
while (true) {
try {
System.out.println("SharedObject: " + a.getObj().getVal());
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SharedObject {
private String val = null;
public SharedObject() {
}
public String getVal() {
return val;
}
public void setVal(String val) {
this.val = val;
}
}
Un méchant piège que j'ai trouvé dans Java est que plusieurs threads accèdent à une HashMap sans synchronisation. S'il y en a une qui lit et l'autre qui écrit, il y a de fortes chances que le lecteur se retrouve dans une infinité loop (la structure de la liste des nœuds du compartiment est corrompue en une liste en boucle).
Évidemment, vous ne devriez pas faire cela en premier lieu (utilisez ConcurrentHashMap ou Collections.synch ... wrapper), mais il semble que ce soit celui qui passe toujours à travers le réseau et provoque le blocage du thread, système complètement cassé, généralement à cause à une classe d'utilitaires contenant une telle carte se trouvant à quelques niveaux de la pile et personne n'y pensant.
Assistance à la mise en œuvre d'acteurs fonctionnels Java) et analyse comparative de millions de threads sur des machines multicœurs.
Je pense que le problème de concurrence le plus fréquent dans Java est un code qui semble fonctionner jusqu'à présent, bien qu'il ne soit pas vraiment thread-safe. Grâce à une petite erreur, il devient une bombe à retardement et dans presque tous les cas, vous ne le savez pas d'avance, car ce n'est pas évident pour vous. Tandis que le code défectueux normal échoue pendant les tests, le code concurrent échoue souvent et finalement ne peut pas être reproduit.
while(true)
{
if (...)
break
doStuff()
}
Invariablement, lorsque les développeurs écrivent lors d'une boucle, ils omettent la "validation de ressource" dans leur propre code.
À savoir si ce bloc ne se ferme pas, l'application et peut-être même le système vont se verrouiller et mourir. Juste à cause d'une simple while(fantasy_land)...if(...) break
.