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.
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. 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:
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