Inhaltsverzeichnis
Einführung
1. Verwendungsdemonstration: Viele Schüler haben ThreadLocal möglicherweise noch nicht verwendet. Die Demo lautet wie folgt:
public class ThreadLocal {}
Nach dem Login kopieren
" >
public class ThreadLocal {}
Nach dem Login kopieren
ThreadLocal hat mehrere Schlüsselattribute, schauen wir sie uns einzeln an:
2.2.1, ThreadLocalMap
3. Wie erreicht ThreadLocal die Datenisolation zwischen Threads?
Wir achten auf einige Punkte im obigen Quellcode:
Wenn die Anzahl der ThreadLocalMap den Schwellenwert überschreitet, werfen wir einen Blick auf die Logik der Erweiterung:
Heim Java javaLernprogramm Detaillierte Erklärung und Quellcode-Analyse von ThreadLocal in der Java-Programmierung

Detaillierte Erklärung und Quellcode-Analyse von ThreadLocal in der Java-Programmierung

Apr 21, 2023 pm 03:19 PM
java threadlocal

    Einführung

    ThreadLocal bietet eine Möglichkeit, dass in einer Multithread-Umgebung jeder Thread seine eigenen eindeutigen Daten haben und während des gesamten Thread-Ausführungsprozesses von oben nach unten übergeben werden kann.

    1. Verwendungsdemonstration: Viele Schüler haben ThreadLocal möglicherweise noch nicht verwendet. Die Demo lautet wie folgt:

    /**
     * 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
    Nach dem Login kopieren
    : aus dem Kontext gewonnen.

    Die getFromComtext-Methode akzeptiert keine Eingabeparameter, context.get().get("key1"), der Wert von key1 wird aus dem Kontext abgerufen Die unterste Ebene von ThreadLocal ist implementiert.

    2. Klassenstruktur

    ThreadLocal definiert die Klasse mit Generika und gibt an, dass ThreadLocal Daten in jedem Format speichern kann:

    public class ThreadLocal<T> {}
    Nach dem Login kopieren

    ThreadLocal hat mehrere Schlüsselattribute, schauen wir sie uns einzeln an:

    // 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();
    Nach dem Login kopieren
    Nach dem Login kopieren

    Es ​​gibt ein weiteres wichtiges Attribut: ThreadLocalMap. Wenn ein Thread mehrere ThreadLocals hat, ist ein Container erforderlich, um mehrere ThreadLocals zu verwalten der Thread.

    2.2.1, ThreadLocalMap

    ThreadLocalMap selbst ist eine einfache Map-Struktur, der Schlüssel ist ThreadLocal, der Wert ist der von ThreadLocal gespeicherte Wert, die unterste Ebene ist die Datenstruktur des Arrays, der Quellcode lautet wie folgt:

    // 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();
    Nach dem Login kopieren
    Nach dem Login kopieren

    Aus dem Quellcode geht hervor, dass ThreadLocalMap tatsächlich eine einfache Kartenstruktur ist. Die unterste Ebene ist ein Array mit einer Initialisierungsgröße und einer Erweiterungsschwellenwertgröße. Das Element des Arrays ist Eintrag, der Schlüssel von Eintrag ist eine Referenz auf ThreadLocal , und der Wert ist der Wert von ThreadLocal.

    3. Wie erreicht ThreadLocal die Datenisolation zwischen Threads?

    ThreadLocal ist Thread-sicher und wir können es mit Sicherheit verwenden, hauptsächlich weil ThreadLocalMap eine Eigenschaft von Threads ist folgt:

    Aus dem Bild oben können wir sehen, dass ThreadLocals.ThreadLocalMap und InheritableThreadLocals.ThreadLocalMap Eigenschaften von Threads sind, sodass die ThreadLocals jedes Threads isoliert und exklusiv sind.

    Wenn der übergeordnete Thread einen untergeordneten Thread erstellt, kopiert er den Wert von inheritableThreadLocals, jedoch nicht den Wert von threadLocals. Der Quellcode lautet wie folgt:

    Detaillierte Erklärung und Quellcode-Analyse von ThreadLocal in der Java-Programmierung

    Aus der obigen Abbildung können wir sehen, wann der Wenn ein Thread erstellt wird, wird der inheritableThreadLocals-Attributwert des übergeordneten Threads kopiert.

    4. Set-Methode

    Detaillierte Erklärung und Quellcode-Analyse von ThreadLocal in der Java-ProgrammierungDie Hauptfunktion der Set-Methode besteht darin, den Wert im aktuellen ThreadLocal festzulegen Der Code lautet wie folgt:

    // 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);
    }
    Nach dem Login kopieren

    Die Codelogik ist relativ klar. Schauen wir uns den Quellcode von ThreadLocalMap.set wie folgt an:

    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();
    }
    Nach dem Login kopieren

    Wir achten auf einige Punkte im obigen Quellcode:

    verwendet einen inkrementierenden AtomicInteger als HashCode von ThreadLocal;

      Die Formel zur Berechnung der Array-Indexposition lautet: hashCode modulo die Größe des Arrays, wird höchstwahrscheinlich ein anderer hashCode die Indexposition berechnen gleiches Array (aber machen Sie sich darüber keine Sorgen. In tatsächlichen Projekten ist ThreadLocal sehr selten und im Grunde kein Konflikt); Suchen Sie weiter rückwärts durch +1, bis eine leere Indexposition gefunden wird, und geben Sie den aktuellen ThreadLocal als Schlüssel ein.
    • Glücklicherweise verwenden wir bei der täglichen Arbeit mit ThreadLocals oft nur 1 bis 2 ThreadLocals, und die Wahrscheinlichkeit doppelter Arrays, die durch Hashing berechnet werden, ist nicht sehr hoch.
    • Die Strategie zur Lösung von Array-Element-Positionskonflikten während des Set-Vorgangs wirkt sich auch auf die get-Methode aus. Schauen wir uns gemeinsam die get-Methode an.

    • 5. get-Methode
    • Die get-Methode ruft hauptsächlich den im aktuellen ThreadLocalMap gespeicherten Wert ab:

      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();
      }
      Nach dem Login kopieren
    • Dann schauen wir uns die getEntry-Methode von ThreadLocalMap an folgt:
    // 得到当前 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);
    }
    Nach dem Login kopieren
    // 自旋 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;
    }
    Nach dem Login kopieren

    Kommentare im Get-Logic-Quellcode Es wurde sehr klar geschrieben, daher werden wir es nicht noch einmal wiederholen.

    6. Erweiterung

    Wenn die Anzahl der ThreadLocalMap den Schwellenwert überschreitet, werfen wir einen Blick auf die Logik der Erweiterung:

    //扩容
    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;
    }
    Nach dem Login kopieren

    Die Quellcode-Anmerkungen sind ebenfalls relativ klar zwei Punkte:

    Nach der Erweiterung ist die Größe des Arrays doppelt so groß wie die des ursprünglichen Arrays.

    Es gibt absolut kein Thread-Sicherheitsproblem während der Erweiterung, da ThreadLocalMap ein Attribut des Threads ist und ein Thread dies kann Arbeiten Sie nur gleichzeitig an ThreadLocalMap, da derselbe Thread Die Ausführung der Geschäftslogik muss seriell sein, daher muss auch die Operation von ThreadLocalMap seriell sein.

    Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung und Quellcode-Analyse von ThreadLocal in der Java-Programmierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

    Erklärung dieser Website
    Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

    Heiße KI -Werkzeuge

    Undresser.AI Undress

    Undresser.AI Undress

    KI-gestützte App zum Erstellen realistischer Aktfotos

    AI Clothes Remover

    AI Clothes Remover

    Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

    Undress AI Tool

    Undress AI Tool

    Ausziehbilder kostenlos

    Clothoff.io

    Clothoff.io

    KI-Kleiderentferner

    AI Hentai Generator

    AI Hentai Generator

    Erstellen Sie kostenlos Ai Hentai.

    Heißer Artikel

    R.E.P.O. Energiekristalle erklärten und was sie tun (gelber Kristall)
    4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. Beste grafische Einstellungen
    4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. So reparieren Sie Audio, wenn Sie niemanden hören können
    4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. Chat -Befehle und wie man sie benutzt
    4 Wochen vor By 尊渡假赌尊渡假赌尊渡假赌

    Heiße Werkzeuge

    Notepad++7.3.1

    Notepad++7.3.1

    Einfach zu bedienender und kostenloser Code-Editor

    SublimeText3 chinesische Version

    SublimeText3 chinesische Version

    Chinesische Version, sehr einfach zu bedienen

    Senden Sie Studio 13.0.1

    Senden Sie Studio 13.0.1

    Leistungsstarke integrierte PHP-Entwicklungsumgebung

    Dreamweaver CS6

    Dreamweaver CS6

    Visuelle Webentwicklungstools

    SublimeText3 Mac-Version

    SublimeText3 Mac-Version

    Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

    Perfekte Zahl in Java Perfekte Zahl in Java Aug 30, 2024 pm 04:28 PM

    Leitfaden zur perfekten Zahl in Java. Hier besprechen wir die Definition, Wie prüft man die perfekte Zahl in Java?, Beispiele mit Code-Implementierung.

    Zufallszahlengenerator in Java Zufallszahlengenerator in Java Aug 30, 2024 pm 04:27 PM

    Leitfaden zum Zufallszahlengenerator in Java. Hier besprechen wir Funktionen in Java anhand von Beispielen und zwei verschiedene Generatoren anhand ihrer Beispiele.

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

    Leitfaden für Weka in Java. Hier besprechen wir die Einführung, die Verwendung von Weka Java, die Art der Plattform und die Vorteile anhand von Beispielen.

    Smith-Nummer in Java Smith-Nummer in Java Aug 30, 2024 pm 04:28 PM

    Leitfaden zur Smith-Zahl in Java. Hier besprechen wir die Definition: Wie überprüft man die Smith-Nummer in Java? Beispiel mit Code-Implementierung.

    Fragen zum Java Spring-Interview Fragen zum Java Spring-Interview Aug 30, 2024 pm 04:29 PM

    In diesem Artikel haben wir die am häufigsten gestellten Fragen zu Java Spring-Interviews mit ihren detaillierten Antworten zusammengestellt. Damit Sie das Interview knacken können.

    Brechen oder aus Java 8 Stream foreach zurückkehren? Brechen oder aus Java 8 Stream foreach zurückkehren? Feb 07, 2025 pm 12:09 PM

    Java 8 führt die Stream -API ein und bietet eine leistungsstarke und ausdrucksstarke Möglichkeit, Datensammlungen zu verarbeiten. Eine häufige Frage bei der Verwendung von Stream lautet jedoch: Wie kann man von einem Foreach -Betrieb brechen oder zurückkehren? Herkömmliche Schleifen ermöglichen eine frühzeitige Unterbrechung oder Rückkehr, aber die Stream's foreach -Methode unterstützt diese Methode nicht direkt. In diesem Artikel werden die Gründe erläutert und alternative Methoden zur Implementierung vorzeitiger Beendigung in Strahlverarbeitungssystemen erforscht. Weitere Lektüre: Java Stream API -Verbesserungen Stream foreach verstehen Die Foreach -Methode ist ein Terminalbetrieb, der einen Vorgang für jedes Element im Stream ausführt. Seine Designabsicht ist

    Zeitstempel für Datum in Java Zeitstempel für Datum in Java Aug 30, 2024 pm 04:28 PM

    Anleitung zum TimeStamp to Date in Java. Hier diskutieren wir auch die Einführung und wie man Zeitstempel in Java in ein Datum konvertiert, zusammen mit Beispielen.

    Java -Programm, um das Kapselvolumen zu finden Java -Programm, um das Kapselvolumen zu finden Feb 07, 2025 am 11:37 AM

    Kapseln sind dreidimensionale geometrische Figuren, die aus einem Zylinder und einer Hemisphäre an beiden Enden bestehen. Das Volumen der Kapsel kann berechnet werden, indem das Volumen des Zylinders und das Volumen der Hemisphäre an beiden Enden hinzugefügt werden. In diesem Tutorial wird erörtert, wie das Volumen einer bestimmten Kapsel in Java mit verschiedenen Methoden berechnet wird. Kapselvolumenformel Die Formel für das Kapselvolumen lautet wie folgt: Kapselvolumen = zylindrisches Volumenvolumen Zwei Hemisphäre Volumen In, R: Der Radius der Hemisphäre. H: Die Höhe des Zylinders (ohne die Hemisphäre). Beispiel 1 eingeben Radius = 5 Einheiten Höhe = 10 Einheiten Ausgabe Volumen = 1570,8 Kubikeinheiten erklären Berechnen Sie das Volumen mithilfe der Formel: Volumen = π × R2 × H (4

    See all articles