Comment ThreadLocal de Java est-il implémenté sous le capot?

Comment ThreadLocal est-il implémenté? Est-il implémenté en Java (en utilisant une carte concurrente de ThreadID à l’object), ou utilise-t-il un hook JVM pour le faire plus efficacement?

Toutes les réponses ici sont correctes, mais un peu décevantes car elles dissimulent un peu la ThreadLocal de l’implémentation de ThreadLocal . Je regardais juste le code source de ThreadLocal et ThreadLocal été agréablement impressionné par la façon dont il a été implémenté.

L’implémentation naïve

Si je vous demandais d’implémenter une ThreadLocal de l’API décrite dans le javadoc, que feriez-vous? Une implémentation initiale serait probablement une ConcurrentHashMap utilisant Thread.currentThread() comme clé. Cette volonté fonctionnerait raisonnablement bien mais présente des inconvénients.

  • Contention sur les threads – ConcurrentHashMap est une classe plutôt intelligente, mais elle a finalement pour conséquence d’empêcher plusieurs threads de la compacter, et si différents threads le frappent régulièrement, il y aura des ralentissements.
  • Garde en permanence un pointeur à la fois sur le thread et l’object, même après la fin du thread et peut être GC ‘.

La mise en œuvre favorable au CA

Ok réessayez, traitons le problème de la récupération de place en utilisant des références faibles . Traiter avec WeakReferences peut être déroutant, mais il devrait suffire d’utiliser une carte comme celle-ci:

  Collections.synchronizedMap(new WeakHashMap()) 

Ou si nous utilisons Guava (et nous devrions l’être!):

 new MapMaker().weakKeys().makeMap() 

Cela signifie qu’une fois que personne d’autre ne s’accroche au thread (ce qui implique qu’il est terminé), la clé / valeur peut être récupérée, ce qui est une amélioration, mais ne résout toujours pas le problème de contention de thread. c’est incroyable d’une classe. De plus, si quelqu’un décidait de s’accrocher aux objects Thread après qu’ils aient fini, ils ne seraient jamais GC, et donc nos objects non plus, même s’ils sont techniquement inaccessibles maintenant.

La mise en œuvre intelligente

Nous avons pensé à ThreadLocal comme à un mappage de threads à des valeurs, mais ce n’est peut-être pas la bonne façon d’y penser. Au lieu de le considérer comme un mappage de Threads vers des valeurs dans chaque object ThreadLocal, que faire si nous y réfléchissions comme un mappage d’objects ThreadLocal sur des valeurs dans chaque Thread ? Si chaque thread stocke le mappage et que ThreadLocal fournit simplement une interface agréable dans ce mappage, nous pouvons éviter tous les problèmes des implémentations précédentes.

Une implémentation ressemblerait à ceci:

 // called for each thread, and updated by the ThreadLocal instance new WeakHashMap() 

Il n’y a pas besoin de s’inquiéter de la concurrence ici, car un seul thread accédera jamais à cette carte.

Les développeurs Java ont un avantage majeur sur nous ici: ils peuvent directement développer la classe Thread et y append des champs et des opérations, et c’est exactement ce qu’ils ont fait.

Dans java.lang.Thread il y a les lignes suivantes:

 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; 

Ce qui, comme le suggère le commentaire, est en effet un mappage privé de paquet de toutes les valeurs suivies par les objects ThreadLocal pour ce Thread . L’implémentation de ThreadLocalMap n’est pas une WeakHashMap , mais elle suit le même contrat de base, y compris en maintenant ses clés par référence faible.

ThreadLocal.get() est alors implémenté comme ceci:

 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 

Et ThreadLocal.setInitialValue() comme ça:

 private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } 

Essentiellement, utilisez une carte dans ce thread pour contenir tous nos objects ThreadLocal . De cette façon, nous n’avons jamais à nous soucier des valeurs des autres threads ( ThreadLocal ne peut littéralement accéder qu’aux valeurs du thread actuel) et n’a donc pas de problèmes de concurrence. De plus, une fois que le Thread est terminé, sa carte sera automatiquement GC et tous les objects locaux seront nettoyés. Même si le Thread est maintenu, les objects ThreadLocal sont ThreadLocal dans une référence faible et peuvent être nettoyés dès que l’object ThreadLocal est hors de scope.


Inutile de dire que j’ai été plutôt impressionné par cette implémentation, qui contourne avec élégance de nombreux problèmes de concurrence (en profitant du fait de faire partie du kernel Java, mais cela leur est pardonnable, car c’est une classe intelligente). access sécurisé aux threads aux objects qui ne doivent être accessibles que par un thread à la fois.

L’implémentation de ThreadLocal est plutôt cool et beaucoup plus rapide / intelligente que vous ne le pensez au premier abord.

Si vous avez aimé cette réponse, vous pouvez également apprécier ma discussion (moins détaillée) sur ThreadLocalRandom .

ThreadLocal code Thread / ThreadLocal issus de l’implémentation de Java 8 par Oracle / OpenJDK .

Vous voulez dire java.lang.ThreadLocal . C’est assez simple, en fait, c’est juste une carte des paires nom-valeur stockées dans chaque object Thread (voir le champ Thread.threadLocals ). L’API cache ce détail d’implémentation, mais c’est tout ce qu’il y a à voir.

Les variables ThreadLocal de Java fonctionnent en accédant à une HashMap détenue par l’instance Thread.currentThread ().

Supposons que vous allez implémenter ThreadLocal , comment le rendez-vous spécifique au thread? Bien sûr, la méthode la plus simple consiste à créer un champ non statique dans la classe Thread, appelons cela threadLocals . Chaque thread étant représenté par une instance de thread, les threadLocals de chaque thread sont également différents. Et c’est aussi ce que fait Java:

 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; 

Qu’est-ce que ThreadLocal.ThreadLocalMap ici? Comme vous ne disposez que d’un threadLocals pour un thread, si vous prenez simplement threadLocals comme ThreadLocal (par exemple, définissez threadLocals comme Integer ), vous n’aurez qu’un seul ThreadLocal pour un thread spécifique. Que faire si vous voulez plusieurs variables ThreadLocal pour un thread? Le moyen le plus simple est de faire de threadLocals un HashMap , la key de chaque entrée est le nom de la variable ThreadLocal et la value de chaque entrée est la valeur de la variable ThreadLocal . Un peu déroutant? Disons que nous avons deux threads, t1 et t2 . ils prennent la même instance de Runnable que le paramètre du constructeur Thread , et ils ont tous deux deux variables tlA nommées tlA et tlb . C’est comme ça.

t1.tlA

 +-----+-------+ | Key | Value | +-----+-------+ | tlA | 0 | | tlB | 1 | +-----+-------+ 

t2.tlB

 +-----+-------+ | Key | Value | +-----+-------+ | tlA | 2 | | tlB | 3 | +-----+-------+ 

Notez que les valeurs sont composées par moi.

Maintenant, cela semble parfait. Mais qu’est-ce que ThreadLocal.ThreadLocalMap ? Pourquoi n’a-t-il pas simplement utilisé HashMap ? Pour résoudre le problème, voyons ce qui se passe lorsque nous définissons une valeur via la méthode set(T value) de la classe ThreadLocal :

 public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 

getMap(t) renvoie simplement t.threadLocals . Comme t.threadLocals été initialisé à null , nous entrons tout d’ createMap(t, value) dans createMap(t, value) :

 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 

Il crée une nouvelle instance ThreadLocalMap utilisant l’instance actuelle de ThreadLocal et la valeur à définir. Voyons ce qu’est ThreadLocalMap , c’est en fait une partie de la classe ThreadLocal

 static class ThreadLocalMap { /** * The ensortinges in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (ie entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such ensortinges are referred to * as "stale ensortinges" in the code that follows. */ static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } ... /** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } ... } 

Le cœur de la classe ThreadLocalMap est la Entry class , qui étend WeakReference . Cela garantit que si le thread actuel se ferme, il sera automatiquement récupéré. C’est pourquoi il utilise ThreadLocalMap au lieu d’un simple HashMap . Il passe le ThreadLocal actuel et sa valeur comme paramètre de la classe Entry , donc lorsque nous voulons obtenir la valeur, nous pouvons l’obtenir de la table , qui est une instance de la classe Entry :

 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 

C’est ce qui se passe dans l’image entière:

L'image entière

Conceptuellement, vous pouvez penser à un ThreadLocal contenant une Map qui stocke les valeurs spéci fi ques aux threads, bien que ce ne soit pas la manière dont elle est réellement implémentée.

Les valeurs spécifiques aux threads sont stockées dans l’object Thread lui-même. Lorsque le thread se termine, les valeurs spéci fi ques du thread peuvent être récupérées.

Référence: JCIP