43 votes

Est-ce vraiment mon travail de nettoyer les ressources ThreadLocal lorsque les classes ont été exposées à un pool de threads?

Mon utilisation de ThreadLocal

Dans mes classes Java, il m'arrive de faire usage d'un ThreadLocal principalement comme un moyen d'éviter la création d'un objet:

@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 fais cela pour la bonne raison - GregorianCalendar est l'un de ces glorieusement dynamique, mutable, non thread-safe objets, qui fournit un service à travers de multiples appels, plutôt que de représenter une valeur. En outre, il est considéré comme "cher" pour instancier (si cela est vrai ou non n'est pas le point de cette question). (Dans l'ensemble, j'ai vraiment l'admirer :-))

Comment Tomcat Whinges

Cependant, si j'utilise une classe dans un environnement qui les pools de threads - et où ma demande n'est pas dans le contrôle du cycle de vie de ces threads - ensuite, il ya le potentiel pour des fuites de mémoire. Un environnement de Servlet est un bon exemple.

En fait, Tomcat 7 whinges comme quand une webapp est arrêté:

GRAVE: L'application web [] créé un ThreadLocal avec clé de type [org.apache.xmlbeans.impl.magasin.CharUtil$1] (valeur [org.apache.xmlbeans.impl.magasin.CharUtil$1@2aace7a7]) et une valeur de type [java.lang.réf.SoftReference] (valeur [java.lang.ref.SoftReference@3d9c9ad4]), mais pas réussi à le retirer lors de la l'application web a été arrêté. Les Threads vont être renouvelés plus de temps d'essayer d'éviter une probable fuite de mémoire. Dec 13, 2012 12:54:30 PM org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks

(Même pas mon code de le faire, dans ce cas particulier).

Qui est à blâmer?

Cela ne semble pas juste. Tomcat est de blâmer moi (ou l'utilisateur de ma classe) pour faire la bonne chose.

En fin de compte, c'est parce que Tomcat veut réutiliser les threads, il m'a offert, pour d'autres applications web. (Ugh - je me sens sale.) Probablement, ce n'est pas un grand politique sur Tomcat, en partie parce que les threads a/cause de l'état - ne partagez pas 'em entre les applications.

Cependant, cette politique est d'au moins commun, même s'il n'est pas souhaitable. J'ai l'impression que je suis obligé - en ThreadLocal utilisateur, pour fournir un moyen pour les élèves de ma classe de "libérer" les ressources qui ma classe a attaché à plusieurs fils.

Mais ce qu'il faut faire à ce sujet?

Qu'est-ce que la bonne chose à faire ici?

Pour moi, il semble que le moteur de servlet fil de la réutilisation de la politique est en contradiction avec l'intention d' ThreadLocal.

Mais je devrais peut-être mettre en place un mécanisme pour permettre aux utilisateurs de dire "retire-toi, le mal de thread spécifique de l'état associé à cette classe, même si je ne suis pas en mesure de laisser le fil de mourir et de laisser GC faire sa chose?". Est-il encore possible pour moi de faire cela? Je veux dire, c'est pas comme si je peux m'arranger pour ThreadLocal#remove() d'être appelé sur chacun des Fils qui vit ThreadLocal#initialValue() , à un certain moment dans le passé. Ou est-il un autre moyen?

Ou devrais-je dire à mes utilisateurs "aller et vous obtenez un décent chargeur de classe et le pool de threads mise en œuvre"?

EDIT#1: Clarifier la façon dont threadCal est utilisé dans un vanailla classe utilitaire qui ignorent fil des cycles de vie EDIT#2: correction d'un thread d'un problème de sécurité en DateSensitiveThing

32voto

David Bullock Points 2120

Soupir, c'est de vieilles nouvelles

Eh bien, un peu en retard à la fête sur ce point. En octobre 2007, Josh Bloch (co-auteur de l' java.lang.ThreadLocal avec Doug Lea) a écrit:

"L'utilisation de pools de threads demande un soin extrême. Sloppy utilisation de thread piscines en combinaison avec bâclée utilisation de fil les habitants peuvent causer des involontaire objet de rétention, comme il a été mentionné dans de nombreux endroits."

Les gens se sont plaints de la mauvaise interaction de ThreadLocal avec des pools de threads même alors. Mais Josh n'sanction:

"Par thread cas pour la performance. Aaron SimpleDateFormat exemple (ci-dessus) est un exemple de ce modèle."

Quelques Leçons

  1. Si vous mettez tous les types d'objets dans n'importe quel objet de la piscine, vous devez fournir un moyen de les éliminer "plus tard".
  2. Si vous "pool" à l'aide d'un ThreadLocal, vous avez peu d'options pour le faire. Soit: a) vous savez que l' Thread(s) où vous mettez les valeurs prendra fin lorsque votre application est terminée; OU b) vous pouvez ensuite organiser pour le même thread qui a appelé ThreadLocal#set() pour appeler ThreadLocal#remove() à chaque fois que votre application s'arrête
  3. En tant que tel, l'utilisation de ThreadLocal comme un objet de la piscine va payer un lourd prix à la conception de votre application et de votre classe. Les avantages ne viennent pas gratuitement.
  4. En tant que tel, l'utilisation de ThreadLocal est probablement une optimisation prématurée, même si Joshua Bloch vous a exhorté à considérer dans "Effective Java".

En bref, de décider d'utiliser une ThreadLocal comme une forme de jeûne, uncontended l'accès à l' "par thread instance de piscines" n'est pas une décision à prendre à la légère.

REMARQUE: Il existe d'autres utilisations de ThreadLocal autre que 'objet de piscines, et ces leçons ne s'appliquent pas à ces scénarios où la ThreadLocal est uniquement destiné à être fixé sur une base temporaire, de toute façon, ou où il y a un véritable par thread de l'état de garder une trace de.

Les conséquences pour la Bibliothèque des réalisateurs

Threre sont quelques-unes des conséquences de la bibliothèque des réalisateurs (même si ces bibliothèques sont de simples classes utilitaires dans votre projet).

Soit:

  1. Vous utilisez ThreadLocal, pleinement conscient de ce que vous pourriez 'polluer' long-threads en cours d'exécution avec un supplément de bagages. Si vous êtes à la mise en œuvre de java.util.concurrent.ThreadLocalRandom, il pourrait être approprié. (Tomcat peut encore whinge les utilisateurs de votre bibliothèque, si vous n'êtes pas la mise en œuvre, en java.*). Il est intéressant de noter la discipline java.* rend l'utilisation économe de l'ThreadLocal technique.

OU

  1. Vous utilisez ThreadLocal, et d'offrir aux clients de votre classe/paquet: a) la possibilité de choisir de renoncer à cette optimisation ("ne pas utiliser ThreadLocal ... je ne peux pas organiser de nettoyage"); ET b) un moyen de nettoyer les ThreadLocal ressources ("c'est OK pour utiliser ThreadLocal ... je peux m'arranger pour tous les Threads qui sert à invoquer LibClass.releaseThreadLocalsForThread() quand j'en aurai terminé avec eux.

Rend votre bibliothèque de mal à utiliser correctement", bien.

OU

  1. Vous donnez à vos clients la possibilité de fournir leur propre objet-piscine impelementation (qui peuvent utiliser ThreadLocal, ou la synchronisation de quelque sorte). ("OK, je peux vous donner un new ExpensiveObjectFactory<T>() { public T get() {...} } si vous pensez que c'est vraiment neccesasry".

Pas si mauvais. Si l'objet est vraiment important et coûteux à créer, explicite la mise en commun est probablement la peine.

OU

  1. Vous décidez qu'il ne vaut pas grand-chose à votre application de toute façon, et de trouver une façon différente d'aborder le problème. Ces coûteux à créer, mutable, non thread-safe objets sont à l'origine de votre douleur ... est de les utiliser vraiment la meilleure option de toute façon?

Alternatives

  1. Objet régulier de mise en commun, avec tous ses valoir de synchronisation.
  2. Pas de mise en commun des objets - il suffit de les instancier dans une portée locale et le jeter plus tard.
  3. Pas de mise en commun des threads (à moins que vous pouvez planifier le nettoyage de code quand vous le souhaitez) - n'utilisez pas vos trucs dans un conteneur JaveEE
  4. Les pools de threads qui sont assez intelligents pour le nettoyage ThreadLocals sans whinging à vous.
  5. Les pools de threads qui répartissent les Threads sur un "par application" base, puis les laisser mourir lorsque l'application est arrêtée.
  6. Un protocole entre le fil de la mise en commun des conteneurs et des applications qui permettent l'enregistrement de " l'arrêt de l'application de gestionnaire, qui le contenant pourrait annexe à exécuter sur les Threads qui avait été utilisé pour le service de l'application ... à un certain moment dans l'avenir, lorsque le Thread est disponible suivant. Par exemple. servletContext.addThreadCleanupHandler(new Handler() {@Override cleanup() {...}})

Il serait agréable de voir une certaine standardisation autour de ces 3 derniers éléments, à l'avenir JavaEE specs.

Bootnote

En fait, l'instanciation d'un GregorianCalendar est assez léger. C'est l'inévitable appel à l' setTime() qui effectue la plupart des travaux. Il également n'a pas de significative de l'état entre les différents points d'un fil de exeuction. Mettre un Calendar en ThreadLocal est peu probable pour vous donner plus que ce qu'il vous en coûte ... à moins que le profilage montre certainement un point chaud en new GregorianCalendar().

new SimpleDateFormat(String) est cher par comparaison, parce qu'il doit analyser la chaîne de format. Une fois analysée, l'état de l'objet est importante pour des utilisations plus tard par le même thread. C'est un meilleur ajustement. Mais il pourrait encore être "moins coûteuse" pour instancier un nouveau, que de donner vos classes des responsabilités supplémentaires.

4voto

Alexei Kaigorodov Points 5841

Depuis le thread n'a pas été créé par vous, il n'a été loué par vous, je pense qu'il est juste d'exiger que pour le nettoyer avant de cesser d'utiliser, comme vous remplit le réservoir d'une voiture de location lors de leur retour. Tomcat pourrait juste nettoyer tout lui-même, mais il ne vous une faveur, rappelant oublié des choses.

AJOUTER: La façon dont vous utilisez préparé GregorianCalendar est tout simplement faux: comme les demandes de service peuvent être simultanées, et il n'y a pas de synchronisation, doCalc peut prendre en getTime ater setTime invoquée par une autre requête. L'introduction de la synchronisation rendrait les choses plus lent, de sorte que la création d'un nouveau GregorianCalendar pourrait être une meilleure option.

En d'autres termes, votre question devrait être: comment garder la piscine de préparé GregorianCalendar instances de sorte que son nombre est ajusté aux taux de demande. Donc au minimum, vous avez besoin d'un singleton qui contient la piscine. Chaque conteneur Ioc a les moyens de gérer un singleton, et la plupart ont un prêt de l'objet de la piscine implémentations. Si vous n'avez pas encore utiliser un conteneur IoC, de commencer à utiliser une (Chaîne, Guice), plutôt que de réinventer la roue.

0voto

Evgeniy Dorofeev Points 52031

Je pense que du JDK de ThreadPoolExecutor pourrait faire ThreadLocals de nettoyage après l'exécution de la tâche, mais comme nous le savons, il n'est pas. Je pense qu'elle pourrait avoir fourni au moins une option. La raison pour laquelle peut-être parce que le Thread ne fournit que des colis privé, l'accès à ses TreadLocal cartes, donc ThreadPoolExecutor juste ne peut pas y accéder sans changer de Fil de l'API.

Il est intéressant de noter, ThreadPoolExecutor a protégé méthode talons beforeExecution et afterExecution, l'API dit: These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals.... Donc, je peux imaginer une Tâche qui met en œuvre un ThreadLocalCleaner interface et notre coutume ThreadPoolExecutor que sur afterExecution appels de la tâche cleanThreadLocals();

0voto

David Bullock Points 2120

Après avoir réfléchi à cela pendant un an, j'ai décidé qu'il n'est pas acceptable pour un conteneur JavaEE pour partager pool de threads de travail entre les instances de l'onu-les applications connexes. Ce n'est pas d '"entreprise" à tous.

Si vous êtes vraiment l'intention de partager des fils autour, java.lang.Thread (dans un JavaEE de l'environnement, au moins) devrait méthodes de soutien, comme setContextState(int key) et forgetContextState(int key) (mirroring setClasLoaderContext()), qui permettent de conteneur pour isoler spécifiques à l'application ThreadLocal état, comme il les mains le fil entre les différentes applications.

Dans l'attente de ces modifications dans l' java.lang d'espace de noms, il n'est utile pour l'application de déployeurs à adopter un "thread-piscine, un exemple des applications liées à la règle, et pour les développeurs d'applications à supposer que "ce fil est le mien jusqu'à ce que ThreadDeath nous faire part'.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X