Dans mes classes Java Java, j'utilise parfois un ThreadLocal
principalement pour éviter la création inutile d'objets:
@net.jcip.annotations.ThreadSafe
public class DateSensitiveThing {
private final Date then;
public DateSensitiveThing(Date then) {
this.then = then;
}
private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>() {
@Override
protected Calendar initialValue() {
return new GregorianCalendar();
}
};
public Date doCalc(int n) {
Calendar c = threadCal.get();
c.setTime(this.then):
// use n to mutate c
return c.getTime();
}
}
Je le fais pour la bonne raison - GregorianCalendar
est l'un de ces objets glorieusement à état, modifiables et non threadsafe, qui fournit un service sur plusieurs appels, plutôt que de représenter une valeur. De plus, il est considéré comme "coûteux" d'instancier (que ce soit vrai ou non n'est pas le point de cette question). (Dans l'ensemble, je l'admire vraiment :-))
Cependant, si j'utilise une telle classe dans n'importe quel environnement qui regroupe des threads - et où mon application ne contrôle pas le cycle de vie de ces threads - alors il y a un potentiel de fuites de mémoire. Un environnement Servlet en est un bon exemple.
En fait, Tomcat 7 pivote comme ça quand une webapp est arrêtée:
GRAVE: l'application Web [] a créé un ThreadLocal avec une clé de type [org.Apache.xmlbeans.impl.store.CharUtil $ 1] (valeur [org.Apache.xmlbeans.impl.store.CharUtil$1@2aace7a7]) et une valeur de type [Java.lang.ref.SoftReference] (valeur [Java.lang.ref.SoftReference@3d9c9ad4]) mais n'a pas pu le supprimer lors de l'arrêt de l'application Web. Les threads vont être renouvelés au fil du temps pour essayer d'éviter une fuite de mémoire probable. 13 décembre 2012 12:54:30 PM org.Apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks
(Pas même mon code qui le fait, dans ce cas particulier).
Cela ne semble guère juste. Tomcat blâme moi (ou l'utilisateur de ma classe) pour avoir fait la bonne chose.
En fin de compte, c'est parce que Tomcat veut réutiliser les threads qu'il m'a proposés, pour autres applications Web. (Ugh - je me sens sale.) Probablement, ce n'est pas une excellente politique de la part de Tomcat - parce que les threads ont réellement/cause l'état - ne les partagez pas entre les applications.
Cependant, cette politique est au moins courante, même si elle n'est pas souhaitable. Je sens que je suis obligé - en tant qu'utilisateur ThreadLocal
, de fournir à ma classe un moyen de "libérer" les ressources que ma classe a attachées à divers threads.
Quelle est la bonne chose à faire ici?
Pour moi, il semble que la politique de réutilisation des threads du moteur de servlet soit en contradiction avec l'intention derrière ThreadLocal
.
Mais peut-être devrais-je fournir une fonctionnalité pour permettre aux utilisateurs de dire "fini, mauvais état spécifique au thread associé à cette classe, même si je ne suis pas en mesure de laisser le thread mourir et de laisser GC faire son travail?". Est-il même possible pour moi de le faire? Je veux dire, ce n'est pas comme si je pouvais arranger que ThreadLocal#remove()
soit appelé sur chacun des Threads qui ont vu ThreadLocal#initialValue()
à un moment donné dans le passé. Ou existe-t-il un autre moyen?
Ou devrais-je simplement dire à mes utilisateurs "allez vous procurer un chargeur de classe décent et une implémentation de pool de threads"?
EDIT # 1: Clarification de la façon dont threadCal
est utilisé dans une classe utilitaire vanailla qui ignore les cycles de vie des threads EDIT # 2: Correction d'un problème de sécurité des threads dans DateSensitiveThing
Eh bien, un peu tard pour la fête sur celui-ci. En octobre 2007, Josh Bloch (co-auteur de Java.lang.ThreadLocal
Avec Doug Lea) écrit :
"L'utilisation de pools de threads nécessite une extrême prudence. L'utilisation bâclée des pools de threads en combinaison avec une utilisation bâclée des sections locales de threads peut entraîner une rétention d'objet involontaire, comme cela a été noté à de nombreux endroits."
Les gens se plaignaient même de la mauvaise interaction de ThreadLocal avec les pools de threads. Mais Josh a sanctionné:
"Instances par thread pour les performances. L'exemple d'Aaron SimpleDateFormat (ci-dessus) est un exemple de ce modèle."
ThreadLocal
, vous avez des options limitées pour le faire. Soit: a) vous savez que les Thread
(s) où vous mettez les valeurs se termineront lorsque votre application sera terminée; OR b) vous pouvez par la suite organiser le même thread qui a appelé ThreadLocal # set () pour appeler ThreadLocal # remove ( ) chaque fois que votre demande se termineEn bref, décider d'utiliser un ThreadLocal comme une forme d'accès rapide et incontrôlé aux "pools d'instances par thread" n'est pas une décision à prendre à la légère.
REMARQUE: il existe d'autres utilisations de ThreadLocal que les "pools d'objets", et ces leçons ne s'appliquent pas aux scénarios dans lesquels ThreadLocal est uniquement destiné à être défini de manière temporaire de toute façon, ou lorsqu'il existe un véritable état par thread à conserver piste de.
Il y a quelques conséquences pour les implémenteurs de bibliothèques (même lorsque ces bibliothèques sont de simples classes utilitaires dans votre projet).
Soit:
Java.util.concurrent.ThreadLocalRandom
, Cela peut être approprié. (Tomcat peut encore pleurnicher sur les utilisateurs de votre bibliothèque, si vous n'implémentez pas dans Java.*
). Il est intéressant de noter la discipline avec laquelle Java.*
Utilise avec parcimonie la technique ThreadLocal.OR
LibClass.releaseThreadLocalsForThread()
quand j'en ai fini avec eux.Rend votre bibliothèque "difficile à utiliser correctement", cependant.
OR
new ExpensiveObjectFactory<T>() { public T get() {...} }
si vous pensez que c'est vraiment nécessaire").Pas si mal. Si l'objet est vraiment si important et si cher à créer, la mise en commun explicite en vaut probablement la peine.
OR
servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})
Ce serait bien de voir une certaine normalisation autour des 3 derniers éléments, dans les futures spécifications JavaEE.
En fait, l'instanciation d'un GregorianCalendar
est assez légère. C'est l'appel inévitable à setTime()
qui entraîne la majeure partie du travail. Il ne contient pas non plus d'état significatif entre les différents points de l'exécution d'un thread. Mettre un Calendar
dans un ThreadLocal
ne vous rapportera probablement pas plus qu'il ne vous en coûte ... à moins que le profilage ne montre définitivement un point chaud dans new GregorianCalendar()
.
new SimpleDateFormat(String)
est cher en comparaison, car il doit analyser la chaîne de format. Une fois analysé, l '"état" de l'objet est significatif pour les utilisations ultérieures par le même thread. C'est un meilleur ajustement. Mais il pourrait toujours être "moins cher" d'instancier une nouvelle que de donner à vos classes des responsabilités supplémentaires.
Étant donné que le fil n'a pas été créé par vous, il a seulement été loué par vous, je pense qu'il est juste d'exiger de le nettoyer avant de cesser de l'utiliser - tout comme vous remplissez le réservoir d'une voiture de location au retour. Tomcat pourrait simplement tout nettoyer lui-même, mais cela vous rend service, rappelant les choses oubliées.
ADD: La façon dont vous utilisez GregorianCalendar préparé est tout simplement erronée: puisque les demandes de service peuvent être simultanées et qu'il n'y a pas de synchronisation, doCalc
peut prendre getTime
ater setTime
invoqué par une autre demande . L'introduction de la synchronisation ralentirait les choses, de sorte que la création d'un nouveau GregorianCalendar
pourrait être une meilleure option.
En d'autres termes, votre question devrait être: comment conserver le pool d'instances GregorianCalendar
préparées de sorte que son nombre soit ajusté au taux de demande. Donc, au minimum, vous avez besoin d'un singleton qui contient ce pool. Chaque conteneur Ioc a les moyens de gérer un singleton, et la plupart ont des implémentations de pool d'objets prêtes. Si vous n'utilisez pas encore de conteneur IoC, commencez à en utiliser un (String, Guice), plutôt que de réinventer la roue.
Si son aide, j'utilise un SPI (une interface) personnalisé et le JDK ServiceLoader
. Ensuite, toutes mes diverses bibliothèques internes (jars) qui doivent faire le déchargement de threadlocals juste suivez le modèle ServiceLoader. Donc, si un pot a besoin d'un nettoyage de threadlocal, il sera automatiquement sélectionné s'il a le /META-INF/services/interface.name
.
Ensuite, je fais le déchargement dans un filtre ou un écouteur (j'ai eu quelques problèmes avec les écouteurs mais je ne me souviens pas quoi).
Ce serait idéal si le JDK/JEE était livré avec un standard SPI pour effacer les threads locaux.
Je pense que ThreadPoolExecutor de JDK pourrait nettoyer ThreadLocals après l'exécution de la tâche, mais comme nous le savons, ce n'est pas le cas. Je pense que cela aurait pu fournir au moins une option. La raison pourrait être que Thread ne fournit qu'un accès privé au package à ses cartes TreadLocal, de sorte que ThreadPoolExecutor ne peut tout simplement pas y accéder sans modifier l'API de Thread.
Fait intéressant, ThreadPoolExecutor a protégé les stubs de méthode beforeExecution
et afterExecution
, l'API dit: These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals...
. Je peux donc imaginer une tâche qui implémente une interface ThreadLocalCleaner et notre ThreadPoolExecutor personnalisé qui, après l'exécution, appelle cleanThreadLocals () de la tâche;
Après y avoir réfléchi pendant un an, j'ai décidé qu'il n'est pas acceptable qu'un conteneur JavaEE partage des threads de travail regroupés entre des instances d'applications non liées. Ce n'est pas du tout une "entreprise".
Si vous allez vraiment partager des discussions, Java.lang.Thread
(Dans un environnement JavaEE, au moins) devrait prendre en charge des méthodes telles que setContextState(int key)
et forgetContextState(int key)
(mise en miroir setClasLoaderContext()
), qui permet au conteneur d'isoler l'état ThreadLocal spécifique à l'application, car il transfère le thread entre diverses applications.
En attendant de telles modifications dans l'espace de noms Java.lang
, Il est logique que les déployeurs d'applications adoptent une règle "un pool de threads, une instance d'applications associées" et que les développeurs d'applications supposent que "ce thread est le mien jusqu'à ce que ThreadDeath nous part ".