Maison  >  Article  >  Java  >  Comment utiliser la classe Java ThreadLocal

Comment utiliser la classe Java ThreadLocal

王林
王林avant
2023-05-14 18:49:061092parcourir

    Comme le montre l'image :

    Comment utiliser la classe Java ThreadLocal

    Démarrage rapide

    Ensuite, nous utiliserons un exemple simple pour vous montrer l'utilisation de base de ThreadLocal

    package cuit.pymjl.thradlocal;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/7/1 10:56
     **/
    public class MainTest {
        static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
        static void print(String str) {
            //打印当前线程中本地内存中本地变量的值
            System.out.println(str + " :" + threadLocal.get());
            //清除本地内存中的本地变量
            threadLocal.remove();
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    threadLocal.set("thread1 local variable");
                    //调用打印方法
                    print("thread1");
                    //打印本地变量
                    System.out.println("after remove : " + threadLocal.get());
                }
            });
    
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置线程1中本地变量的值
                    threadLocal.set("thread2 local variable");
                    //调用打印方法
                    print("thread2");
                    //打印本地变量
                    System.out.println("after remove : " + threadLocal.get());
                }
            });
    
            t1.start();
            t2.start();
        }
    }

    Les résultats en cours sont tels qu'indiqués dans la figure montrée :

    Comment utiliser la classe Java ThreadLocal

    Le principe de ThreadLocal

    Diagramme de classe associé à ThreadLocal

    Jetons d'abord un coup d'œil à la structure du diagramme de classe de la classe associée à ThreadLocal, comme indiqué dans l'image :

    Comment utiliser la classe Java ThreadLocal

    On peut voir sur cette image qu'il existe un threadLocals et un ThreadLocals hérités dans la classe Thread, qui sont des variables de type ThreadLocalMap, et ThreadLocalMap est une Hashmap personnalisée. Par défaut, ces deux variables dans chaque thread sont nulles et elles sont créées uniquement lorsque le thread actuel appelle la méthode set ou get de ThreadLocal pour la première fois. En fait, les variables locales de chaque thread ne sont pas stockées dans l'instance ThreadLocal, mais dans la variable threadLocals du thread appelant. En d’autres termes, les variables locales de type ThreadLocal sont stockées dans des espaces mémoire de thread spécifiques. ThreadLocal est un shell d'outil qui place la valeur dans les threadLocals du thread appelant via la méthode set et la stocke lorsque le thread appelant appelle sa méthode get, il la retire de la variable threadLocals du thread actuel pour l'utiliser. Si le thread appelant ne se termine jamais, alors cette variable locale sera toujours stockée dans la variable threadLocals du thread appelant. Par conséquent, lorsque la variable locale n'est pas nécessaire, vous pouvez supprimer la variable locale des threadLocals du thread actuel en appelant le. Remove méthode de la variable ThreadLocal. De plus, pourquoi les threadLocals dans Thread sont-ils conçus comme une structure de carte ? Évidemment parce que chaque thread peut être associé à plusieurs variables ThreadLocal. Ensuite, jetons un coup d'œil au code source de Set, Get et Remove de ThreadLocal

    set

        public void set(T value) {
            // 1.获取当前线程(调用者线程)
            Thread t = Thread.currentThread();
            // 2.以当前线程作为key值,去查找对应的线程变量,找到对应的map
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 3.如果map不为null,则直接添加元素
                map.set(this, value);
            } else {
                // 4.否则就先创建map,再添加元素
                createMap(t, value);
            }
        }
        void createMap(Thread t, T firstValue) {
            /**
             * 这里是创建一个ThreadLocalMap,以当前调用线程的实例对象为key,初始值为value
             * 然后放入当前线程的Therad.threadLocals属性里面
             */
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        ThreadLocalMap getMap(Thread t) {
            //这里就是直接获取调用线程的成员属性threadlocals
            return t.threadLocals;
        }

    get

        public T get() {
            // 1.获取当前线程
            Thread t = Thread.currentThread();
            // 2.获取当前线程的threadlocals,即ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            // 3.如果map不为null,则直接返回对应的值
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            // 4.否则,则进行初始化
            return setInitialValue();
        }

    Ce qui suit est le code de setInitialValuesetInitialValue的代码

    private T setInitialValue() {
        //初始化属性,其实就是null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //通过当前线程获取ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //如果map不为null,则直接添加元素
        if (map != null) {
            map.set(this, value);
        } else {
            //否则就创建,然后将创建好的map放入当前线程的属性threadlocals
            createMap(t, value);
        }
            //将当前ThreadLocal实例注册进TerminatingThreadLocal类里面
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    这里我需要补充说明一下TerminatingThreadLocal

    /**
     * A thread-local variable that is notified when a thread terminates and
     * it has been initialized in the terminating thread (even if it was
     * initialized with a null value).
     * 一个线程局部变量,
     * 当一个线程终止并且它已经在终止线程中被初始化时被通知(即使它被初始化为一个空值)。
     */

    Je dois en ajouter explications supplémentaires ici TerminatingThreadLocal. Cette classe est nouvelle dans jdk11 et n'existe pas dans jdk8, il n'y a donc aucune description pertinente de cette classe dans de nombreuses analyses de code source sur Internet. J'ai jeté un œil au code source de cette classe, et sa fonction devrait être d'éviter le problème des fuites de mémoire ThreadLocal (si vous êtes intéressé, vous pouvez vérifier le code source, et veuillez me corriger s'il y a des erreurs). Voici l'explication officielle :

         public void remove() {
             //如果当前线程的threadLocals 变量不为空, 则删除当前线程中指定ThreadLocal 实例的本地变量。
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null) {
                 m.remove(this);
             }
         }

    remove

    rrreee

    Summary

    Il y a une variable membre nommée threadLocals à l'intérieur de chaque thread. Le type de cette variable est Hash Map, où la clé est la variable ThreadLocal que nous avons définie cette référence, valeur. est la valeur que nous avons définie à l'aide de la méthode set. Les variables locales de chaque thread sont stockées dans la propre variable de mémoire du thread threadLocals. Si le thread actuel ne meurt jamais, ces variables locales existeront toujours, cela peut donc provoquer un débordement de mémoire. Par conséquent, n'oubliez pas d'appeler la méthode Remove de ThreadLocal pour la supprimer après. utiliser. Variables locales dans threadLocals correspondant au thread.

    Fuite de mémoire ThreadLocal

    Pourquoi une fuite de mémoire se produit-elle ?

    ThreadLocalMap utilise la référence faible de ThreadLocal comme clé. Si un ThreadLocal n'a pas de référence forte externe pour s'y référer, alors le ThreadLocal sera inévitablement recyclé lors du GC du système De cette façon, une entrée avec un null. key apparaîtra dans le ThreadLocalMap , il n'y a aucun moyen d'accéder à la valeur de ces entrées avec des clés nulles Si le fil de discussion en cours continue à se terminer, les valeurs de ces entrées avec des clés nulles auront toujours une chaîne de référence forte : Thread Ref -> Thread - > ThreaLocalMap -> La valeur de l'entrée ne peut jamais être recyclée, provoquant une fuite de mémoire.

    En fait, cette situation a été prise en compte dans la conception de ThreadLocalMap, et quelques mesures de protection ont été ajoutées : toutes les valeurs avec des clés nulles dans le thread ThreadLocalMap seront effacées lors de get(), set() et delete () de ThreadLocal. Cependant, ces mesures préventives passives ne garantissent pas qu'il n'y aura pas de fuites de mémoire :
    • L'utilisation de ThreadLocal statique prolonge le cycle de vie de ThreadLocal, ce qui peut entraîner des fuites de mémoire
    • L'allocation utilise ThreadLocal et n'appelle plus get( ), set(), remove(), cela provoquera des fuites de mémoire

    Pourquoi utiliser des références faibles ?

    Puisque nous savons tous que l'utilisation de références faibles entraînera des fuites de mémoire ThreadLocalMap, pourquoi les responsables utilisent-ils encore des références faibles au lieu de références fortes ? Cela commence par la différence entre utiliser des références faibles et des références fortes : 🎜
    • Si vous utilisez des références fortes : nous savons que le cycle de vie de ThreadLocalMap est fondamentalement le même que le cycle de vie de Thread. Si le thread actuel n'est pas terminé, alors ThreadLocalMap ne sera jamais recyclé par GC et ThreadLocalMap détient une forte référence. référence à ThreadLocal, alors ThreadLocal Il ne sera pas recyclé. Lorsque le cycle de vie du thread est long, s'il n'est pas supprimé manuellement, cela provoquera une accumulation de kv, ce qui entraînera un MOO

    • Si vous utilisez des références faibles : objets dans des références faibles. avoir une période de déclaration courte, car dans le système Pendant le GC, tant qu'une référence faible est trouvée, l'objet sera recyclé, qu'il y ait ou non suffisamment d'espace sur le tas. Lorsque la référence forte de ThreadLocal est recyclée, la référence faible détenue par ThreadLocalMap sera également recyclée. Si kv n'est pas supprimé manuellement, cela provoquera une accumulation de valeur et conduira également à OOM

    . l'utilisation de références faibles peut au moins garantir que le MOO ne sera pas causé par l'accumulation de clés de carte et que la valeur correspondante peut être effacée lors du prochain appel via les méthodes Remove, Get et Set. On peut voir que la cause première des fuites de mémoire ne réside pas dans les références faibles, mais que le cycle de vie de ThreadLocalMap est aussi long que celui de Thread, provoquant une accumulation

    Solution

    Puisque la racine du problème est l'accumulation de valeurs. provoquant du MOO, nous pouvons alors prescrire le bon médicament et nous assurer qu'il est utilisé après chaque utilisation. Il suffit d'appeler la méthode remove() de ThreadLocal pour le nettoyer.

    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