Mit diesem Artikel können Sie ThreadLocal vollständig verstehen

Dieser Artikel wurde von der Huawei Cloud Community geteilt. „ [Hohe Parallelität] Dieser Artikel wird Ihnen helfen, ThreadLocal gründlich zu verstehen “, Autor: Binghe.

Wir alle wissen, dass der Zugriff auf dieselbe gemeinsam genutzte Variable in einer Multithread-Umgebung zu Thread-Sicherheitsproblemen führen kann. Um die Thread-Sicherheit zu gewährleisten, sperren wir häufig beim Zugriff auf diese gemeinsam genutzte Variable, um den Effekt der Synchronisierung zu erzielen, wie in der folgenden Abbildung gezeigt .

cke_150.png

Obwohl das Sperren gemeinsamer Variablen die Sicherheit von Threads gewährleisten kann, erhöht es die Fähigkeiten der Entwickler im Umgang mit Sperren. Wenn Sperren nicht ordnungsgemäß verwendet werden, führt dies zu Deadlock-Problemen. Und ThreadLocal kann erreichen, dass nach dem Erstellen der Variablen jeder Thread beim Zugriff auf die Variable auf die lokale Variable des Threads selbst zugreift .

Was ist ThreadLocal?

ThreadLocal wird vom JDK bereitgestellt und unterstützt Thread-lokale Variablen. Das heißt, wenn wir eine ThreadLocal-Variable erstellen, verfügt jeder Thread, der auf die Variable zugreift, über eine lokale Kopie der Variablen. Wenn mehrere Threads diese Variable gleichzeitig lesen und schreiben, bearbeiten sie tatsächlich die Variable im eigenen lokalen Speicher des Threads, wodurch das Problem der Thread-Sicherheit vermieden wird.

cke_151.png

Beispiel für die Verwendung von ThreadLocal

Beispielsweise verwenden wir ThreadLocal, um zugehörige Variableninformationen zu speichern und zu drucken. Das Programm sieht wie folgt aus.

öffentliche Klasse ThreadLocalTest {

private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

public static void main(String[] args){

//Erstelle den ersten Thread

Thread threadA = new Thread(()->{

threadLocal.set("ThreadA:" + Thread.currentThread().getName());

System.out.println("Der Wert in der lokalen Variablen von Thread A ist: " + threadLocal.get());

});

//Erstelle einen zweiten Thread

Thread threadB = new Thread(()->{

threadLocal.set("ThreadB:" + Thread.currentThread().getName());

System.out.println("Der Wert in der lokalen Variablen von Thread B ist: " + threadLocal.get());

});

//Thread A und Thread B starten

threadA.start();

threadB.start();

}

}

Führen Sie das Programm aus und die gedruckten Ergebnisinformationen lauten wie folgt.

Der Wert in der lokalen Variablen Thread A ist: ThreadA:Thread-0

Der Wert in der lokalen Variablen Thread B lautet: ThreadB:Thread-1

An diesem Punkt fügen wir den Vorgang zum Löschen von Variablen in ThreadLocal zu Thread A hinzu, wie unten gezeigt.

öffentliche Klasse ThreadLocalTest {

private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

public static void main(String[] args){

//Erstelle den ersten Thread

Thread threadA = new Thread(()->{

threadLocal.set("ThreadA:" + Thread.currentThread().getName());

System.out.println("Der Wert in der lokalen Variablen von Thread A ist: " + threadLocal.get());

threadLocal.remove();

System.out.println("Nachdem Thread A die lokale Variable gelöscht hat, lautet der Wert in ThreadLocal: " + threadLocal.get());

});

//Erstelle einen zweiten Thread

Thread threadB = new Thread(()->{

threadLocal.set("ThreadB:" + Thread.currentThread().getName());

System.out.println("Der Wert in der lokalen Variablen von Thread B ist: " + threadLocal.get());

System.out.println("Thread B hat die lokale Variable nicht gelöscht: " + threadLocal.get());

});

//Thread A und Thread B starten

threadA.start();

threadB.start();

}

}

Das Ergebnis der Operation zu diesem Zeitpunkt ist wie folgt.

Der Wert in der lokalen Variablen Thread A ist: ThreadA:Thread-0

Der Wert in der lokalen Variablen Thread B lautet: ThreadB:Thread-1

Thread B hat die lokale Variable ThreadB:Thread-1 nicht gelöscht

Nachdem Thread A die lokale Variable gelöscht hat, lautet der Wert in ThreadLocal: null

Durch das obige Programm können wir sehen, dass sich die von Thread A und Thread B in ThreadLocal gespeicherten Variablen nicht gegenseitig stören, auf die von Thread A gespeicherten Variablen nur Thread A und nur die von Thread B gespeicherten Variablen zugreifen können Der Zugriff erfolgt über Thread B.

Prinzip von ThreadLocal

Schauen wir uns zunächst den Quellcode der Thread-Klasse an, wie unten gezeigt.

Die öffentliche Klasse Thread implementiert Runnable {

/**********N Zeilen Code weglassen************/

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

/**********N Zeilen Code weglassen************/

}

Aus dem Quellcode der Thread-Klasse ist ersichtlich, dass die ThreadLocal-Klasse Mitgliedsvariablen threadLocals und inheritableThreadLocals enthält. Diese beiden Mitgliedsvariablen sind Variablen vom Typ ThreadLocalMap, und die Anfangswerte beider sind null. Die Variable wird nur instanziiert, wenn der aktuelle Thread die set()-Methode oder get()-Methode von ThreadLocal zum ersten Mal aufruft.

Hierbei ist zu beachten, dass die lokalen Variablen jedes Threads nicht in der ThreadLocal-Instanz, sondern in der ThreadLocals-Variablen des aufrufenden Threads gespeichert werden. Das heißt, die durch Aufrufen der set()-Methode von ThreadLocal gespeicherte lokale Variable wird im Speicherbereich des jeweiligen Threads gespeichert, und die ThreadLocal-Klasse stellt nur die Methoden set() und get() zum Speichern und Lesen des Werts bereit der lokalen Variablen. Wenn die set()-Methode der ThreadLocal-Klasse aufgerufen wird, wird der zu speichernde Wert in den threadLocals des aufrufenden Threads gespeichert, und wenn die get()-Methode der ThreadLocal-Klasse aufgerufen wird, wird der gespeicherte Wert gespeichert wird aus der Variable threadLocals des aktuellen Threads entnommen.

Als nächstes analysieren wir die Implementierungslogik der Methoden set(), get() und remove() der ThreadLocal-Klasse.

set()-Methode

Der Quellcode der set()-Methode wird unten angezeigt.

public void set(T value) {

// Den aktuellen Thread abrufen

Thread t = Thread.currentThread();

//Verwenden Sie den aktuellen Thread als Schlüssel, um das ThreadLocalMap-Objekt abzurufen

ThreadLocalMap map = getMap(t);

//Das erhaltene ThreadLocalMap-Objekt ist nicht leer

if (map != null)

//Setze den Wert von value

map.set(this, value);

anders

// Das erhaltene ThreadLocalMap-Objekt ist leer. Erstellen Sie die Variable threadLocals in der Thread-Klasse

createMap(t, value);

}

Rufen Sie in der set()-Methode zunächst den Thread ab, der die set()-Methode aufruft, und verwenden Sie dann den aktuellen Thread als Schlüssel, um die getMap(t)-Methode aufzurufen und das ThreadLocalMap-Objekt abzurufen. Der Quellcode des getMap(Thread t) Methode ist wie folgt.

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

Es ist ersichtlich, dass die Methode getMap (Thread t) die ThreadLocals-Mitgliedsvariable der Thread-Variablen selbst abruft.

Wenn in der set()-Methode das durch den Aufruf der getMap(t)-Methode zurückgegebene Objekt nicht leer ist, wird der Wert auf die ThreadLocals-Membervariable der Thread-Klasse gesetzt und der übergebene Schlüssel ist das this-Objekt des aktuellen ThreadLocal. und der Wert wird über die set()-Methode übergeben, um den Wert zu übergeben.

Wenn das durch den Aufruf der getMap(t)-Methode zurückgegebene Objekt leer ist, ruft das Programm die createMap(t, value)-Methode auf, um die ThreadLocals-Membervariable der Thread-Klasse zu instanziieren.

void createMap(Thread t, T firstValue) {

t.threadLocals = new ThreadLocalMap(this, firstValue);

}

Das heißt, die threadLocals-Variable des aktuellen Threads zu erstellen.

get()-Methode

Der Quellcode der get()-Methode wird unten angezeigt.

öffentliches T get() {

// Den aktuellen Thread abrufen

Thread t = Thread.currentThread();

//Holen Sie sich die ThreadLocals-Mitgliedsvariable des aktuellen Threads

ThreadLocalMap map = getMap(t);

//Die erhaltene ThreadLocals-Variable ist nicht leer

if (map != null) {

//den Wert der lokalen Variablen zurückgeben

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings("ungeprüft")

T result = (T)e.value;

Ergebnis zurückgeben;

}

}

//Initialisieren Sie den Wert der ThreadLocals-Mitgliedsvariablen

return setInitialValue();

}

Rufen Sie die Mitgliedsvariable threadLocals über den aktuellen Thread ab. Wenn die Mitgliedsvariable threadLocals nicht leer ist, geben Sie direkt die an den aktuellen Thread gebundene lokale Variable zurück. Andernfalls rufen Sie die Methode setInitialValue () auf, um den Wert der Mitgliedsvariablen threadLocals zu initialisieren.

private T setInitialValue() {

// Rufen Sie die Methode auf, um den Wert zu initialisieren

T-Wert = initialValue();

Thread t = Thread.currentThread();

// Holen Sie sich die ThreadLocals-Mitgliedsvariable entsprechend dem aktuellen Thread

ThreadLocalMap map = getMap(t);

if (map != null)

//threadLocals ist nicht leer, dann legen Sie den Wert fest

map.set(this, value);

anders

//threadLocals ist leer, threadLocals-Variable erstellen

createMap(t, value);

Rückgabewert;

}

Darunter ist der Quellcode der Methode initialValue() wie folgt.

protected T initialValue() {

null zurückgeben;

}

Wie aus dem Quellcode der Methode initialValue () hervorgeht, kann diese Methode von Unterklassen überschrieben werden. In der ThreadLocal-Klasse gibt diese Methode direkt null zurück.

Methode „remove()“.

Der Quellcode der Methode „remove()“ wird unten angezeigt.

öffentliche Leere entfernen() {

// Holen Sie sich die ThreadLocals-Mitgliedsvariable entsprechend dem aktuellen Thread

ThreadLocalMap m = getMap(Thread.currentThread());

if (m != null)

//threadLocals-Mitgliedsvariable ist nicht leer, dann entfernen Sie den Wert value

m.remove(this);

}

Die Implementierung der Methode „remove()“ ist relativ einfach. Rufen Sie zunächst die Mitgliedsvariable „threadLocals“ entsprechend dem aktuellen Thread ab. Wenn sie nicht leer ist, entfernen Sie den Wert von „value“ direkt.

Hinweis: Wenn der aufrufende Thread nicht konsistent beendet wird, wird die lokale Variable immer in der ThreadLocals-Mitgliedsvariablen des aufrufenden Threads gespeichert. Wenn Sie die lokale Variable nicht verwenden müssen, können Sie daher die Methode „remove()“ von aufrufen ThreadLocal zum Entfernen der lokalen Variablen aus dem aktuellen Thread. Aus der ThreadLocals-Mitgliedsvariablen gelöscht, um Speicherüberlaufprobleme zu vermeiden.

ThreadLocal-Variablen sind nicht transitiv

Die Verwendung von ThreadLocal zum Speichern lokaler Variablen ist nicht transitiv. Das heißt, nachdem derselbe ThreadLocal einen Wert im übergeordneten Thread festgelegt hat, kann dieser Wert im untergeordneten Thread nicht abgerufen werden. Dieses Phänomen zeigt, dass die in ThreadLocal gespeicherten lokalen Variablen nicht transitiv sind . .

Schauen wir uns als Nächstes einen Codeabschnitt an, wie unten gezeigt.

öffentliche Klasse ThreadLocalTest {

private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

public static void main(String[] args){

// Setze den Wert im Hauptthread

threadLocal.set("ThreadLocalTest");

//Den Wert im untergeordneten Thread abrufen

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("Der untergeordnete Thread erhält den Wert: " + threadLocal.get());

}

});

//Untergeordneten Thread starten

thread.start();

//Den Wert im Hauptthread abrufen

System.out.println("Der Hauptthread erhält den Wert: " + threadLocal.get());

}

}

Die durch Ausführen dieses Codes ausgegebenen Ergebnisinformationen lauten wie folgt.

Der Hauptthread erhält den Wert: ThreadLocalTest

Untergeordneter Thread erhält Wert: null

Anhand des obigen Programms können wir erkennen, dass nach dem Festlegen eines Werts auf ThreadLocal im Hauptthread dieser Wert im untergeordneten Thread nicht abgerufen werden kann. Gibt es eine Möglichkeit, den vom Hauptthread festgelegten Wert im untergeordneten Thread abzurufen? An diesem Punkt können wir InheritableThreadLocal verwenden, um dieses Problem zu lösen.

Beispiel für die Verwendung von InheritableThreadLocal

Die InheritableThreadLocal-Klasse erbt von der ThreadLocal-Klasse, wodurch der untergeordnete Thread auf den Wert der lokalen Variablen zugreifen kann, die im übergeordneten Thread festgelegt ist. Beispielsweise schreiben wir die statische Variable threadLocal in der ThreadLocalTest-Klasse als Instanz der InheritableThreadLocal-Klasse um unten dargestellt.

öffentliche Klasse ThreadLocalTest {

private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();

public static void main(String[] args){

// Setze den Wert im Hauptthread

threadLocal.set("ThreadLocalTest");

//Den Wert im untergeordneten Thread abrufen

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

System.out.println("Der untergeordnete Thread erhält den Wert: " + threadLocal.get());

}

});

//Untergeordneten Thread starten

thread.start();

//Den Wert im Hauptthread abrufen

System.out.println("Der Hauptthread erhält den Wert: " + threadLocal.get());

}

}

Zu diesem Zeitpunkt lauten die Ergebnisinformationen, die beim Ausführen des Programms ausgegeben werden, wie folgt.

Der Hauptthread erhält den Wert: ThreadLocalTest

Untergeordneter Thread erhält Wert: ThreadLocalTest

Es ist ersichtlich, dass der untergeordnete Thread bei Verwendung der InheritableThreadLocal-Klasse zum Speichern lokaler Variablen die im übergeordneten Thread festgelegten lokalen Variablen abrufen kann.

Prinzip von InheritableThreadLocal

Schauen wir uns zunächst den Quellcode der InheritableThreadLocal-Klasse an, wie unten gezeigt.

öffentliche Klasse InheritableThreadLocal<T> erweitert ThreadLocal<T> {

protected T childValue(T parentValue) {

return parentValue;

}

ThreadLocalMap getMap(Thread t) {

return t.inheritableThreadLocals;

}

void createMap(Thread t, T firstValue) {

t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);

}

}

Gemäß dem Quellcode der InheritableThreadLocal-Klasse erbt die InheritableThreadLocal-Klasse von der ThreadLocal-Klasse und schreibt die childValue()-Methode, die getMap()-Methode und die createMap()-Methode der ThreadLocal-Klasse neu. Das heißt, wenn die set()-Methode von ThreadLocal aufgerufen wird, wird anstelle der ThreadLocals-Mitgliedsvariablen die inheritableThreadLocals-Mitgliedsvariable des aktuellen Thread-Threads erstellt.

Hier müssen wir über eine Frage nachdenken: Wann wird die Methode childValue() der Klasse InheritableThreadLocal aufgerufen?  Dazu müssen wir uns die Konstruktionsmethode der Thread-Klasse ansehen, wie unten gezeigt.

öffentlicher Thread() {

init(null, null, "Thread-" + nextThreadNum(), 0);

}

öffentlicher Thread (ausführbares Ziel) {

init(null, target, „Thread-“ + nextThreadNum(), 0);

}

Thread(Ausführbares Ziel, AccessControlContext acc) {

init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);

}

öffentlicher Thread(ThreadGroup-Gruppe, ausführbares Ziel) {

init(group, target, „Thread-“ + nextThreadNum(), 0);

}

öffentlicher Thread(String-Name) {

init(null, null, name, 0);

}

öffentlicher Thread(ThreadGroup-Gruppe, String-Name) {

init(group, null, name, 0);

}

öffentlicher Thread(Ausführbares Ziel, String-Name) {

init(null, Ziel, Name, 0);

}

öffentlicher Thread(ThreadGroup-Gruppe, ausführbares Ziel, String-Name) {

init(Gruppe, Ziel, Name, 0);

}

öffentlicher Thread(ThreadGroup-Gruppe, ausführbares Ziel, String-Name,

lange Stapelgröße) {

init(group, target, name, stackSize);

}

Es ist ersichtlich, dass der Konstruktor der Thread-Klasse schließlich die Methode init () aufruft. Schauen wir uns also die Methode init () an, wie unten gezeigt.

private void init(ThreadGroup g, Runnable target, String name,

long stackSize, AccessControlContext acc,

boolean inheritThreadLocals) {

/***************Einigen Quellcode weglassen************/

if (inheritThreadLocals && parent.inheritableThreadLocals != null)

this.inheritableThreadLocals =

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

/* Die angegebene Stapelgröße zwischenspeichern, falls es die VM interessiert */

this.stackSize = stackSize;

/* Thread-ID festlegen */

tid = nextThreadID();

}

Es ist ersichtlich, dass in der Methode init () beurteilt wird, ob die übergebene Variable inheritThreadLocals wahr ist und ob die Variable inheritedThreadLocals im übergeordneten Thread null ist inheritableThreadLocals im übergeordneten Thread ist nicht null. Rufen Sie dann die Methode createInheritedMap() der ThreadLocal-Klasse auf.

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {

return new ThreadLocalMap(parentMap);

}

In createInheritedMap() wird ein neues ThreadLocalMap-Objekt erstellt, wobei die inheritableThreadLocals-Variable des übergeordneten Threads als Parameter verwendet wird. Anschließend wird in der init()-Methode der Thread-Klasse dieses ThreadLocalMap-Objekt der Mitgliedsvariablen inheritableThreadLocals des untergeordneten Threads zugewiesen.

Schauen wir uns als Nächstes an, was der ThreadLocalMap-Konstruktor tut, wie unten gezeigt.

private ThreadLocalMap(ThreadLocalMap parentMap) {

Eintrag[] parentTable = parentMap.table;

int len ​​= parentTable.length;

setThreshold(len);

table = neuer Eintrag[len];

for (int j = 0; j < len; j++) {

Eintrag e = parentTable[j];

if (e != null) {

@SuppressWarnings("ungeprüft")

ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();

if (key != null) {

// Rufen Sie die neu geschriebene childValue-Methode auf

Objektwert = key.childValue(e.value);

Eintrag c = neuer Eintrag(Schlüssel, Wert);

int h = key.threadLocalHashCode & (len - 1);

while (table[h] != null)

h = nextIndex(h, len);

Tabelle[h] = c;

Größe++;

}

}

}

}

Im Konstruktor von ThreadLocalMap wird die von der InheritableThreadLocal-Klasse neu geschriebene Methode childValue() aufgerufen. Die InheritableThreadLocal-Klasse speichert die lokalen Variablen in der Variable inheritableThreadLocals des Thread-Threads, indem sie die Methoden getMap() und createMap() neu schreibt. Wenn der Thread die Variable über die Methode set() und die Methode get() von InheritableThreadLocal festlegt In der Klasse wird die aktuelle inheritableThreadLocals-Variable des Threads erstellt. Wenn zu diesem Zeitpunkt der übergeordnete Thread einen untergeordneten Thread erstellt, kopiert der Konstruktor der Thread-Klasse die lokale Variable in die Variable inheritableThreadLocals im übergeordneten Thread und speichert sie in der Variablen inheritableThreadLocals des untergeordneten Threads.

Klicken Sie hier, um zu folgen und zum ersten Mal mehr über die neuen Technologien von Huawei Cloud zu erfahren~

Klarstellung darüber, dass MyBatis-Flex den offiziell veröffentlichten MyBatis-Plus Arc-Browser 1.0 plagiiert und behauptet, ein Ersatz für Chrome zu sein. OpenAI hat offiziell die Android-Version eingeführt. ChatGPT VS Code optimierte Namensverschleierungskomprimierung, reduzierte integrierte JS um 20 %! LK-99: Der erste Raumtemperatur- und Druck-Supraleiter? Musk „kaufte für null Yuan“ und raubte das @x-Twitter-Konto. Der Python-Lenkungsausschuss plant, den PEP 703-Vorschlag anzunehmen und die globale Interpretersperre optional zu machen . Die Anzahl der Besuche der Open-Source- und kostenlosen Paketerfassungssoftware Stack Overflow des Systems ist deutlich gesunken und Musk sagte, es sei durch LLM ersetzt worden
{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4526289/blog/10092234