Table des matières
Introduction
1. Démonstration d'utilisation
2. Structure de classe
2.1. Génériques de classe
2.2.2.2. Attributs de clés, examinons-les un par un :
ThreadLocalMap lui-même est une structure Map simple, la clé est ThreadLocal, la valeur est la valeur enregistrée par ThreadLocal, la couche inférieure est la structure de données du tableau, le code source est le suivant :
ThreadLocal est thread-safe et nous pouvons l'utiliser en toute confiance, principalement parce que ThreadLocalMap est une propriété des threads, comme. suit :
La fonction principale de la méthode set est de définir la valeur dans le ThreadLocal actuel. Si le type générique du ThreadLocal actuel est Map, alors il s'agit de définir la carte dans le ThreadLocal actuel. Le code est le suivant :
La méthode get obtient principalement la valeur stockée dans le ThreadLocal actuel à partir de ThreadLocalMap :
Lorsque le nombre de ThreadLocals dans ThreadLocalMap dépasse le seuil, ThreadLocalMap commencera à se développer :
Maison Java javaDidacticiel Explication détaillée et analyse du code source de ThreadLocal dans la programmation Java

Explication détaillée et analyse du code source de ThreadLocal dans la programmation Java

Apr 21, 2023 pm 03:19 PM
java threadlocal

    Introduction

    ThreadLocal fournit un moyen selon lequel, dans un environnement multithread, chaque thread peut avoir ses propres données uniques et peut être transmis de haut en bas pendant tout le processus d'exécution du thread.

    1. Démonstration d'utilisation

    De nombreux étudiants n'ont peut-être jamais utilisé ThreadLocal auparavant. Démontrons d'abord l'utilisation de ThreadLocal :

    /**
     * ThreadLocal 中保存的数据是 Map
     */
    static final ThreadLocal<Map<String, String>> context = new ThreadLocal<>();
    @Test
    public void testThread() {
      // 从上下文中拿出 Map
      Map<String, String> contextMap = context.get();
      if (CollectionUtils.isEmpty(contextMap)) {
        contextMap = Maps.newHashMap();
      }
      contextMap.put("key1", "value1");
      context.set(contextMap);
      log.info("key1,value1被放到上下文中");
    	// 从上下文中拿出刚才放进去的数据
      getFromComtext();
    }
    private String getFromComtext() {
      String value1 = context.get().get("key1");
      log.info("从 ThreadLocal 中取出上下文,key1 对应的值为:{}", value1);
      return value1;
    }
    //运行结果:
    demo.ninth.ThreadLocalDemo - key1,value1被放到上下文中
    demo.ninth.ThreadLocalDemo - 从 ThreadLocal 中取出上下文,key1 对应的值为:value1
    Copier après la connexion

    Comme vous pouvez le voir sur les résultats en cours, la valeur correspondant à key1 a été. obtenu à partir du contexte.

    La méthode getFromComtext n'accepte aucun paramètre d'entrée. Grâce à cette ligne de code, context.get().get("key1"), la valeur de key1 est obtenue à partir du contexte. ThreadLocal sous-jacent est implémenté.

    2. Structure de classe

    2.1. Génériques de classe

    ThreadLocal définit la classe avec des génériques, indiquant que ThreadLocal peut stocker des données dans n'importe quel format :

    public class ThreadLocal<T> {}
    Copier après la connexion

    2.2.2.2. Attributs de clés, examinons-les un par un :

    // threadLocalHashCode 表示当前 ThreadLocal 的 hashCode,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 计算 ThreadLocal 的 hashCode 值(就是递增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
    private static AtomicInteger nextHashCode = new AtomicInteger();
    Copier après la connexion
    Copier après la connexion

    Il existe un autre attribut important : ThreadLocalMap Lorsqu'un thread a plusieurs ThreadLocals, un conteneur est nécessaire pour gérer plusieurs ThreadLocals. Le rôle de ThreadLocalMap est de gérer plusieurs ThreadLocals dans. le fil.

    2.2.1, ThreadLocalMap

    ThreadLocalMap lui-même est une structure Map simple, la clé est ThreadLocal, la valeur est la valeur enregistrée par ThreadLocal, la couche inférieure est la structure de données du tableau, le code source est le suivant :
    // threadLocalHashCode 表示当前 ThreadLocal 的 hashCode,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 计算 ThreadLocal 的 hashCode 值(就是递增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修饰非常关键,因为一个线程在处理业务的过程中,ThreadLocalMap 是会被 set 多个 ThreadLocal 的,多个 ThreadLocal 就依靠 threadLocalHashCode 进行区分
    private static AtomicInteger nextHashCode = new AtomicInteger();
    Copier après la connexion
    Copier après la connexion

    Voir à partir du code source que ThreadLocalMap est en fait une structure Map simple, la couche inférieure est un tableau, avec une taille d'initialisation et une taille de seuil d'expansion. Les éléments du tableau sont Entry, la clé d'Entrée est la référence de ThreadLocal. , et la valeur est la valeur de ThreadLocal.

    3. Comment ThreadLocal réalise-t-il l'isolation des données entre les threads ?

    ThreadLocal est thread-safe et nous pouvons l'utiliser en toute confiance, principalement parce que ThreadLocalMap est une propriété des threads, comme. suit :

    Explication détaillée et analyse du code source de ThreadLocal dans la programmation JavaDe l'image ci-dessus, nous pouvons voir que ThreadLocals.ThreadLocalMap et InheritableThreadLocals.ThreadLocalMap sont des propriétés de threads, donc les ThreadLocals de chaque thread sont isolés et exclusifs.

    Lorsque le thread parent crée un thread enfant, il copiera la valeur de HeitableThreadLocals, mais ne copiera pas la valeur de threadLocals. Le code source est le suivant :

    Explication détaillée et analyse du code source de ThreadLocal dans la programmation JavaD'après la figure ci-dessus, nous pouvons voir que lorsque le thread est créé, il sera copié. La valeur de l'attribut HeitableThreadLocals du thread parent est copiée.

    4. Méthode Set

    La fonction principale de la méthode set est de définir la valeur dans le ThreadLocal actuel. Si le type générique du ThreadLocal actuel est Map, alors il s'agit de définir la carte dans le ThreadLocal actuel. Le code est le suivant :

    // set 操作每个线程都是串行的,不会有线程安全的问题
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // 当前 thradLocal 之前有设置值,直接设置,否则初始化
        if (map != null)
            map.set(this, value);
        // 初始化ThreadLocalMap
        else
            createMap(t, value);
    }
    Copier après la connexion

    La logique du code est relativement claire. Jetons un coup d'œil au code source de ThreadLocalMap.set, comme suit :

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算 key 在数组中的下标,其实就是 ThreadLocal 的 hashCode 和数组大小-1取余
        int i = key.threadLocalHashCode & (len-1);
     
        // 整体策略:查看 i 索引位置有没有值,有值的话,索引位置 + 1,直到找到没有值的位置
        // 这种解决 hash 冲突的策略,也导致了其在 get 时查找策略有所不同,体现在 getEntryAfterMiss 中
        for (Entry e = tab[i];
             e != null;
             // nextIndex 就是让在不超过数组长度的基础上,把数组的索引位置 + 1
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 找到内存地址一样的 ThreadLocal,直接替换
            if (k == key) {
                e.value = value;
                return;
            }
            // 当前 key 是 null,说明 ThreadLocal 被清理了,直接替换掉
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 当前 i 位置是无值的,可以被当前 thradLocal 使用
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // 当数组大小大于等于扩容阈值(数组大小的三分之二)时,进行扩容
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    Copier après la connexion

    Nous prêtons attention à quelques points dans le code source ci-dessus :

      utilise un AtomicInteger incrémentiel comme hashCode de ThreadLocal ;
    • La formule pour calculer la position de l'index du tableau est : hashCode modulo la taille du tableau. Puisque hashCode continue d'augmenter, différents hashCode calculeront très probablement la position d'index du tableau. même tableau (mais ne vous inquiétez pas pour cela. Dans les projets réels, ThreadLocal est très rare et fondamentalement pas de conflit)
    • S'il y a déjà une valeur à la position d'index i calculée par hashCode, elle commencera à partir de i et continuez à chercher en arrière jusqu'à +1 jusqu'à ce qu'il trouve une position d'index vide, et placez le ThreadLocal actuel comme clé Go in.
    • Heureusement, lorsque nous utilisons ThreadLocals dans le travail quotidien, nous n'utilisons souvent que 1 à 2 ThreadLocals, et la probabilité de tableaux en double calculés par hachage n'est pas très élevée.

    La stratégie de résolution des conflits de position des éléments du tableau pendant l'ensemble affecte également la méthode get. Examinons ensemble la méthode get.

    5. Méthode get

    La méthode get obtient principalement la valeur stockée dans le ThreadLocal actuel à partir de ThreadLocalMap :

    public T get() {
        // 因为 threadLocal 属于线程的属性,所以需要先把当前线程拿出来
        Thread t = Thread.currentThread();
        // 从线程中拿到 ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 从 map 中拿到 entry,由于 ThreadLocalMap 在 set 时的 hash 冲突的策略不同,导致拿的时候逻辑也不太一样
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果不为空,读取当前 ThreadLocal 中保存的值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 否则给当前线程的 ThreadLocal 初始化,并返回初始值 null
        return setInitialValue();
    }
    Copier après la connexion

    Jetons ensuite un coup d'œil à la méthode getEntry de ThreadLocalMap. suit :

    // 得到当前 thradLocal 对应的值,值的类型是由 thradLocal 的泛型决定的
    // 由于 thradLocalMap set 时解决数组索引位置冲突的逻辑,导致 thradLocalMap get 时的逻辑也是对应的
    // 首先尝试根据 hashcode 取模数组大小-1 = 索引位置 i 寻找,找不到的话,自旋把 i+1,直到找到索引位置不为空为止
    private Entry getEntry(ThreadLocal<?> key) {
        // 计算索引位置:ThreadLocal 的 hashCode 取模数组大小-1
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回,否则就是没有找到,继续通过 getEntryAfterMiss 方法找
        if (e != null && e.get() == key)
            return e;
        else
        // 这个取数据的逻辑,是因为 set 时数组索引位置冲突造成的  
            return getEntryAfterMiss(key, i, e);
    }
    Copier après la connexion
    // 自旋 i+1,直到找到为止
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        // 在大量使用不同 key 的 ThreadLocal 时,其实还蛮耗性能的
        while (e != null) {
            ThreadLocal<?> k = e.get();
            // 内存地址一样,表示找到了
            if (k == key)
                return e;
            // 删除没用的 key
            if (k == null)
                expungeStaleEntry(i);
            // 继续使索引位置 + 1
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
    Copier après la connexion

    Commentaires dans le code source get logic Il a été écrit très clairement, nous ne le répéterons donc pas.

    6. Expansion

    Lorsque le nombre de ThreadLocals dans ThreadLocalMap dépasse le seuil, ThreadLocalMap commencera à se développer :

    //扩容
    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 {
                    // 计算 ThreadLocal 在新数组中的位置
                    int h = k.threadLocalHashCode & (newLen - 1);
                    // 如果索引 h 的位置值不为空,往后+1,直到找到值为空的索引位置
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    // 给新数组赋值
                    newTab[h] = e;
                    count++;
                }
            }
        }
        // 给新数组初始化下次扩容阈值,为数组长度的三分之二
        setThreshold(newLen);
        size = count;
        table = newTab;
    }
    Copier après la connexion

    Les annotations du code source sont également relativement claires. deux points :

      Après l'expansion, la taille du tableau est le double de celle du tableau d'origine ;
    • Il n'y a absolument aucun problème de sécurité des threads lors de l'expansion, car ThreadLocalMap est un attribut du thread, et un thread peut ne fonctionne que sur ThreadLocalMap en même temps, car le même thread exécutant la logique métier doit être en série, donc l'exploitation de ThreadLocalMap doit également être en série.

    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 de ce site Web
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

    Outils d'IA chauds

    Undresser.AI Undress

    Undresser.AI Undress

    Application basée sur l'IA pour créer des photos de nu réalistes

    AI Clothes Remover

    AI Clothes Remover

    Outil d'IA en ligne pour supprimer les vêtements des photos.

    Undress AI Tool

    Undress AI Tool

    Images de déshabillage gratuites

    Clothoff.io

    Clothoff.io

    Dissolvant de vêtements AI

    AI Hentai Generator

    AI Hentai Generator

    Générez AI Hentai gratuitement.

    Article chaud

    R.E.P.O. Crystals d'énergie expliqués et ce qu'ils font (cristal jaune)
    4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. Meilleurs paramètres graphiques
    4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. Comment réparer l'audio si vous n'entendez personne
    4 Il y a quelques semaines By 尊渡假赌尊渡假赌尊渡假赌
    WWE 2K25: Comment déverrouiller tout dans Myrise
    1 Il y a quelques mois By 尊渡假赌尊渡假赌尊渡假赌

    Outils chauds

    Bloc-notes++7.3.1

    Bloc-notes++7.3.1

    Éditeur de code facile à utiliser et gratuit

    SublimeText3 version chinoise

    SublimeText3 version chinoise

    Version chinoise, très simple à utiliser

    Envoyer Studio 13.0.1

    Envoyer Studio 13.0.1

    Puissant environnement de développement intégré PHP

    Dreamweaver CS6

    Dreamweaver CS6

    Outils de développement Web visuel

    SublimeText3 version Mac

    SublimeText3 version Mac

    Logiciel d'édition de code au niveau de Dieu (SublimeText3)

    Nombre parfait en Java Nombre parfait en Java Aug 30, 2024 pm 04:28 PM

    Guide du nombre parfait en Java. Nous discutons ici de la définition, comment vérifier le nombre parfait en Java ?, des exemples d'implémentation de code.

    Générateur de nombres aléatoires en Java Générateur de nombres aléatoires en Java Aug 30, 2024 pm 04:27 PM

    Guide du générateur de nombres aléatoires en Java. Nous discutons ici des fonctions en Java avec des exemples et de deux générateurs différents avec d'autres exemples.

    Weka en Java Weka en Java Aug 30, 2024 pm 04:28 PM

    Guide de Weka en Java. Nous discutons ici de l'introduction, de la façon d'utiliser Weka Java, du type de plate-forme et des avantages avec des exemples.

    Numéro de Smith en Java Numéro de Smith en Java Aug 30, 2024 pm 04:28 PM

    Guide du nombre de Smith en Java. Nous discutons ici de la définition, comment vérifier le numéro Smith en Java ? exemple avec implémentation de code.

    Questions d'entretien chez Java Spring Questions d'entretien chez Java Spring Aug 30, 2024 pm 04:29 PM

    Dans cet article, nous avons conservé les questions d'entretien Java Spring les plus posées avec leurs réponses détaillées. Pour que vous puissiez réussir l'interview.

    Break or Return of Java 8 Stream Forach? Break or Return of Java 8 Stream Forach? Feb 07, 2025 pm 12:09 PM

    Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est

    Horodatage à ce jour en Java Horodatage à ce jour en Java Aug 30, 2024 pm 04:28 PM

    Guide de TimeStamp to Date en Java. Ici, nous discutons également de l'introduction et de la façon de convertir l'horodatage en date en Java avec des exemples.

    Programme Java pour trouver le volume de la capsule Programme Java pour trouver le volume de la capsule Feb 07, 2025 am 11:37 AM

    Les capsules sont des figures géométriques tridimensionnelles, composées d'un cylindre et d'un hémisphère aux deux extrémités. Le volume de la capsule peut être calculé en ajoutant le volume du cylindre et le volume de l'hémisphère aux deux extrémités. Ce tutoriel discutera de la façon de calculer le volume d'une capsule donnée en Java en utilisant différentes méthodes. Formule de volume de capsule La formule du volume de la capsule est la suivante: Volume de capsule = volume cylindrique volume de deux hémisphères volume dans, R: Le rayon de l'hémisphère. H: La hauteur du cylindre (à l'exclusion de l'hémisphère). Exemple 1 entrer Rayon = 5 unités Hauteur = 10 unités Sortir Volume = 1570,8 unités cubes expliquer Calculer le volume à l'aide de la formule: Volume = π × r2 × h (4

    See all articles