2017-09-28 1 views
1

I ont la classe Collector et ThreadLocalScope comme ceci:mémoire ancienne génération augmente lors de l'utilisation ThreadLocal

Collector { 
    Collector() { 
     events = new LinkedList<>(); 
    } 

    add(Event e) { 
     events.add(e); 
    } 

    flush() { 
     LinkedList<Event> copy = events; 
     new Thread(() -> { 
      for (Event e : copy) { 
       sendToServer(e); 
      } 
      copy.clear(); 
     ).start(); 

     events = new LinkedList<>(); 
    } 
} 

ThreadLocalScope { 
    public static ThreadLocal<Collector> local = new ThreadLocal<>() { 
     protected Collector initialValue() { 
      return new Collector(); 
     } 
    } 
} 

Collector ajoute simplement des événements et quand flush est appelé envoie ces événements à une API dans un nouveau thread. Le Collector est initialisé dans un ThreadLocal.

J'ai aussi une classe Job qui est exécutée plusieurs fois (en utilisant Quartz). Lorsque défini comme celui-ci tout fonctionne très bien:

Job { 
    execute() { 
     for (int i = 0; i < 100,000; i++) { 
      ThreadLocalScope.get().add(new Event()); 
     } 
     ThreadLocalScope.get().flush(); 
    } 
} 

Cependant, si au lieu que j'accrochons Collector comme ceci:

Job { 
    Collector collector; 
    Job() { 
     collector = ThreadLocalScope.get(); 
    } 

    execute() { 
     for (int i = 0; i < 100,000; i++) { 
      collector.add(new Event()); 
     } 
     collector.flush(); 
    } 
} 

Je vois mon utilisation de la mémoire ancienne génération de plus en plus rapide et d'arrêt du monde cycles de collecte des ordures passe tout le temps. La seule différence est que j'ai ajouté Collector comme variable membre plutôt que d'appeler ThreadLocalScope.get() à chaque fois.

L'augmentation pourrait seulement signifier que les événements sont déplacés dans l'ancienne génération. Mais pourquoi cela arriverait-il? Collector efface immédiatement toutes ses références aux événements, donc même si ce n'est pas GCed, les événements devraient être.

Répondre

0

Je dis:

Je pense que vous pourriez avoir un problème de sécurité ici-fil.

Incorrect. Je pense que c'est plus simple que ça.

Dans la première version, vous appelez ThreadLocalScope.get() dans le contexte du thread qui exécute le travail.

Dans la deuxième version, vous appelez ThreadLocalScope.get() dans le contrext du thread qui crée l'objet Job(). Il est ensuite écrasé dans une variable et utilisé plus tard dans le thread d'exécution. En supposant que les objets Job() sont tous créés sur le même thread, cela signifie que vos méthodes execute() partagent le même objet Collector. Et ils s'exécutent potentiellement sur des threads différents. Étant donné que Collector n'est pas thread-safe, c'est un danger.

Il y a une autre chose que vous pourriez ne pas être au courant. Il est probable que Quartz utilise un pool de threads. Cela signifie que lorsqu'un appel execute() se termine, le thread revient au pool. La prochaine fois, si Quartz utilise le même thread, il réutilisera l'objet Collector de la dernière fois. ThreadLocal signifie qu'un seul thread peut accéder à cette variable.

+0

Chaque thread a donc sa propre instance de 'Collector'. Il ne peut y avoir aucune condition de course correcte? – dg428

+0

oui Quartz réutilise le fil. Mais j'ai vérifié la création du travail et execute() fonctionne sur le même thread. Resuing Collector n'est pas un problème pour le prochain job car je peux effacer les évènements – dg428

+0

Cependant, je pense que c'est à la racine de votre problème. Et pour être honnête, le thread local n'aide pas l'efficacité, et 'flush()' créer des threads comme ça est un gros coup de performance. –