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 simultanée de ThreadID à object), ou utilise-t-il un hook JVM pour le faire plus efficacement?

Author: ripper234, 2009-07-29

5 answers

Toutes les réponses ici sont correctes, mais un peu décevantes car elles montrent à quel point la mise en œuvre de ThreadLocal est intelligente. Je regardais juste le code source pour ThreadLocal et a été agréablement impressionné par la façon dont il est mis en œuvre.

L'Implémentation Naïve

Si je vous demandais d'implémenter une classe ThreadLocal<T> compte tenu de l'API décrite dans le javadoc, que feriez-vous? Une implémentation initiale serait probablement un ConcurrentHashMap<Thread,T> utilisant Thread.currentThread() comme clé. Ce will fonctionnerait raisonnablement bien, mais présente certains inconvénients.

  • Thread contention - ConcurrentHashMap est une classe assez intelligente, mais elle doit finalement encore s'occuper d'empêcher plusieurs threads de s'en mêler de quelque manière que ce soit, et si différents threads le frappent régulièrement, il y aura des ralentissements.
  • Conserve en permanence un pointeur sur le Thread et l'objet, même après que le Thread a terminé et pourrait être GC'ed.

Le GC-friendly Mise en œuvre

Ok essayez à nouveau, permet de traiter le problème de récupération de place en utilisant références faibles . Traiter avec WeakReferences peut être déroutant, mais il devrait suffire d'utiliser une carte construite comme ceci:

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())

Ou si nous utilisons Goyave (et nous devrions être!):

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

Cela signifie qu'une fois que personne d'autre ne tient le 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 le fait toujours pas résoudre le problème de conflit de threads, ce qui signifie que jusqu'à présent, notre ThreadLocal n'est pas si étonnant d'une classe. De plus, si quelqu'un décidait de conserver des objets Thread après avoir terminé, ils ne seraient jamais GC'ed, et donc nos objets 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 peut-être que ce n'est pas vraiment la bonne façon d'y penser. Plutôt de le penser comme un mappage des Threads aux valeurs dans chaque objet ThreadLocal, et si nous le pensions comme un mappage des objets ThreadLocal aux 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<ThreadLocal,T>()

Il n'est pas nécessaire de s'inquiéter de la concurrence ici, car seulement un 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 ajouter 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;

Qui, comme le suggère le commentaire, est en effet un mappage privé de paquets de toutes les valeurs suivies par les objets ThreadLocal pour ce Thread. La mise en œuvre de ThreadLocalMap n'est pas une WeakHashMap, mais il suit le même contrat de base, y compris en tenant ses clés par référence faible.

ThreadLocal.get() est ensuite 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 ceci:

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 cartedans ce fil pour contenir tous nos ThreadLocal objets. De cette façon, nous n'avons jamais besoin de nous soucier des valeurs des autres Threads (ThreadLocal ne peut littéralement accéder qu'aux valeurs du thread actuel) et n'avons donc aucun problème de concurrence. De plus, une fois le Thread terminé, sa carte sera automatiquement GC'ed et tous les objets locaux seront nettoyés. Même si le Thread est maintenu, les objets ThreadLocal sont maintenus par une référence faible et peuvent être nettoyés dès que l'objet ThreadLocal sort de la portée.


Inutile de dire que j'ai été plutôt impressionné par cette implémentation, elle contourne assez élégamment beaucoup de problèmes de concurrence (certes en profitant de faire partie du noyau Java, mais c'est pardonnable comme c'est une classe si intelligente) et permet un accès rapide et sécurisé aux objets qui ne doivent être accessibles que par un thread à la fois.

Tl; dr ThreadLocal'l'implémentation de s est assez cool, et beaucoup plus rapide/plus intelligent que vous ne le pensez à première vue.

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

Thread/ThreadLocal des extraits de code tirés de l'implémentation de Java par Oracle/OpenJDK 8.

 120
Author: dimo414, 2017-05-23 12:34:30

Vous voulez dire java.lang.ThreadLocal. C'est assez simple, vraiment, c'est juste une carte de paires nom-valeur stockées dans chaque objet Thread (voir le champ Thread.threadLocals). L'API cache ce détail d'implémentation, mais c'est plus ou moins tout ce qu'il y a à faire.

 33
Author: skaffman, 2009-07-29 19:20:58

Les variables ThreadLocal en Java fonctionnent en accédant à une HashMap détenue par le Thread.Instance currentThread ().

 8
Author: Chris Vest, 2009-07-29 19:32:32

Supposons que vous allez implémenter ThreadLocal, comment le rendre 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-le threadLocals. Parce que chaque fil est représenté par une instance de thread, donc threadLocals dans chaque thread serait différent. 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? Parce que vous avez seulement un threadLocals pour un thread, donc si vous prenez simplement threadLocals votre ThreadLocal(disons, définissons threadLocals comme Integer), vous n'ont qu'un seul ThreadLocal pour un thread spécifique. Que faire si vous voulez plusieurs ThreadLocal variables pour un thread? La façon la plus simple est de faire threadLocals un HashMap, le key de chaque entrée est le nom de la ThreadLocal variable, et la value de chaque entrée est la valeur de la ThreadLocal variable. Un peu déroutant? Disons que nous avons deux fils, t1 et t2. ils prennent la même instance Runnable que le paramètre du constructeur Thread, et ils ont tous deux deux ThreadLocal variables nommées tlA et tlb. C'est ce que c'est comme.

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, il 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) retourne simplement t.threadLocals. Parce que {[35] } a été initialisé à null, nous entrons donc createMap(t, value) en premier:

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

Il crée une nouvelle instance ThreadLocalMap en utilisant le instance actuelle ThreadLocal et la valeur à définir. Voyons voir à quoi ressemble ThreadLocalMap, il fait en fait partie de la classe ThreadLocal

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** 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);
    }

    ...

}

La partie centrale de la classe ThreadLocalMap est le Entry class, qui étend WeakReference. Il garantit que si le thread actuel se termine, il sera automatiquement collecté. C'est pourquoi il utilise ThreadLocalMap, au lieu d'un simple HashMap. Il passe le courant {[7] } et sa valeur comme paramètre de la classe Entry, donc quand nous voulons obtenir la valeur, nous pourrions l'obtenir à partir de 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();
}

Voici ce qui est comme dans l'ensemble de l'image:

L'Ensemble De L'Image

 4
Author: Searene, 2016-05-12 05:35:27

Conceptuellement, vous pouvez penser à un ThreadLocal<T> comme contenant un Map<Thread,T> qui stocke les valeurs spécifiques au thread, bien que ce ne soit pas ainsi qu'il est réellement implémenté.

Les valeurs spécifiques au thread sont stockées dans l'objet Thread lui-même; lorsque le thread se termine, les valeurs spécifiques au thread peuvent être récupérées.

Référence : JCIP

 -1
Author: bpjoshi, 2017-11-29 21:41:48