Maison  >  Article  >  Java  >  Analyse et introduction au principe d'implémentation de ThreadLocal (avec code)

Analyse et introduction au principe d'implémentation de ThreadLocal (avec code)

不言
不言avant
2019-02-16 13:37:472921parcourir

Ce que cet article vous apporte est une analyse et une introduction au principe de mise en œuvre de ThreadLocal (avec code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer.

ThreadLocal, une variable locale de thread, est utilisée pour conserver une copie indépendante de la variable pour chaque thread qui l'utilise. Cette variable n'est valide que pendant le cycle de vie du thread. Et contrairement au mécanisme de verrouillage, qui échange du temps contre de l'espace, ThreadLocal ne dispose d'aucun mécanisme de verrouillage. Il échange de l'espace contre du temps pour garantir la sécurité des threads des variables.

Cet article analyse le principe d'implémentation de ThreadLocal à partir du code source.

Premier aperçu de la structure du diagramme de classes ThreadLocal

 

SuppliedThreadLocal est principalement utilisé par JDK1.8 pour étendre la prise en charge des expressions Lambda. Si vous êtes intéressé, veuillez vous référer à Baidu.

ThreadLocalMap est la classe interne statique de ThreadLocal et est également la classe qui enregistre réellement les variables.

Entry est la classe interne statique de ThreadLocalMap. ThreadLocalMap contient un tableau Entry, avec ThreadLocal comme clé et la variable comme valeur, encapsulant une Entry.

Le diagramme suivant explique brièvement la relation entre Thread, ThreadLocal, ThreadLocalMap et Entry.

 

Expliquez l'image ci-dessus :

  1. Un Thread a un objet ThreadLocalMap

  2. ThreadLocalMap a un tableau Entry

  3. Chaque entrée a k--v

  4. La clé d'entrée est un objet ThreadLocal spécifique

Les principales méthodes sont analysées ci-dessous.

1. ensemble()

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

On peut le voir ici : un Thread n'a qu'un seul objet ThreadLocalMap ; la valeur stockée spécifique est appelée set() de ThreadLocalMap, et la clé de paramètre transmise est l'objet ThreadLocal actuel.

Regardez la méthode set() de ThreadLocalMap :

private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); // 1

            for (Entry e = tab[i];  // 2
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value); // 3
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
                rehash();
        }

  1. Calculez l'index du tableau en prenant le modulo du hashCode de la clé et de la capacité du tableau -1

  2. Commencez à parcourir à partir de l'index actuel et effacez ceux non valides où la clé est null Entry

  3. Encapsulez K-V en tant qu'Entrée et placez-le dans le tableau

  4. pour déterminer si cela est nécessaire Développez le tableau Entry. La valeur du seuil est de 2/3 de la capacité du réseau.

Jetez un œil à la méthode d'expansion resize() :

private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

L'essentiel ici est d'augmenter la capacité de 2 fois celle d'origine. Parcourez ensuite l'ancien tableau et recalculez la position de l'entrée dans le nouveau tableau en fonction de la nouvelle capacité du tableau.

2. get()

La méthode get() de ThreadLocal est la suivante :

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();
    }

La méthode getEntry() de ThreadLocalMap est la suivante :

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1); // 1
            Entry e = table[i];
            if (e != null && e.get() == key) // 2
                return e;
            else
                return getEntryAfterMiss(key, i, e); //3
        }

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) { //4
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

  1. Calculer l'index

  2. L'entrée sur l'index actuel n'est pas vide et la clé est la même, retournez directement

  3. Sinon, allez dans l'index adjacent pour rechercher

  4. en boucle Si une clé invalide est trouvée, elle sera effacée. Terminez le cycle une fois trouvé.

3. supprimer()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

La méthode de traitement est similaire à la recherche et à l'enregistrement. Après avoir supprimé l'entrée correspondante, les éléments invalides avec des clés nulles seront supprimés.

Remarque

static class Entry extends WeakReference<ThreadLocal<?>> {}

ThreadLocal peut avoir des problèmes de MOO. Étant donné que ThreadLocalMap utilise la référence faible de ThreadLocal comme clé, lorsque GC se produit, la clé est recyclée, nous ne pouvons donc pas accéder à l'élément de valeur avec une clé nulle. Si la valeur elle-même est un objet plus grand, alors si le thread ne se termine pas, la valeur sera Il n'a jamais pu être recyclé. Surtout lorsque nous utilisons le pool de threads, les threads sont réutilisés et les threads ne seront pas tués. De cette façon, lorsque la référence faible ThreadLocal est recyclée, la valeur ne sera pas recyclée.

Lors de l'utilisation de ThreadLocal, la méthode ThreadLocal.remove() doit être explicitement appelée à la fin du code logique du thread.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer