Warum sollten Java ThreadLocal-Variablen statisch sein?

101

Ich habe hier den JavaDoc für Threadlocal gelesen

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

und es heißt "ThreadLocal-Instanzen sind normalerweise private statische Felder in Klassen, die den Status einem Thread zuordnen möchten (z. B. eine Benutzer-ID oder eine Transaktions-ID)."

Aber meine Frage ist, warum sie sich dafür entschieden haben, es statisch zu machen (normalerweise) - es macht die Dinge etwas verwirrend, den Status "pro Thread" zu haben, aber die Felder sind statisch?

kellyfj
quelle

Antworten:

131

Denn wenn es ein Feld auf Instanzebene wäre, wäre es tatsächlich "Pro Thread - Pro Instanz", nicht nur ein garantiertes "Pro Thread". Das ist normalerweise nicht die Semantik, nach der Sie suchen.

Normalerweise enthält es Objekte, die für eine Benutzerkonversation, eine Webanforderung usw. bestimmt sind. Sie möchten nicht, dass sie auch für die Instanz der Klasse gelten.
Eine Webanforderung => eine Persistenzsitzung.
Nicht eine Webanforderung => eine Persistenzsitzung pro Objekt.

Affe
quelle
2
Ich mag diese Erklärung, weil sie zeigt, wie ThreadLocal verwendet werden soll
kellyfj
4
Pro-Thread-pro-Instanz kann eine nützliche Semantik sein, aber die meisten Verwendungen für dieses Muster würden so viele Objekte umfassen, dass es besser wäre, a zu verwenden ThreadLocal, um einen Verweis auf einen Hash-Satz zu halten, der Objekte Instanzen pro Thread zuordnet.
Supercat
@optional Es bedeutet nur, dass jede Instanz des Nicht-Statischen ThreadLocalihre eigenen threadlokalen Daten enthält, selbst wenn diese ThreadLocalInstanzen im selben Thread vorhanden sind. Es ist nicht unbedingt falsch, das zu tun - ich nehme an, es ist vielleicht das am wenigsten populäre Muster der beiden
geg
17

Machen Sie es entweder statisch oder wenn Sie versuchen, statische Felder in Ihrer Klasse zu vermeiden - machen Sie die Klasse selbst zu einem Singleton, und dann können Sie ThreadLocal auf Instanzebene sicher verwenden, solange Sie dieses Singleton global verfügbar haben.

Adnan Memon
quelle
12

Das muss nicht sein. Das Wichtigste ist, dass es ein Singleton sein sollte.

unwiderlegbar
quelle
3

Der Grund ist, dass auf die Variablen über einen dem Thread zugeordneten Zeiger zugegriffen wird. Sie verhalten sich wie globale Variablen mit Thread-Bereich, daher ist statisch die engste Anpassung. Auf diese Weise erhalten Sie den lokalen Thread-Status in Dingen wie pthreads, sodass dies möglicherweise nur ein Unfall der Historie und Implementierung ist.

Ukko
quelle
1

Eine Verwendung für ein Threadlocal auf einer Instanz pro Thread besteht darin, dass etwas in allen Methoden eines Objekts sichtbar sein soll und es threadsicher sein soll, ohne den Zugriff darauf zu synchronisieren, wie Sie es für ein gewöhnliches Feld tun würden.

Chris Mawata
quelle
1

Beziehen Sie sich darauf , dies gibt ein besseres Verständnis.

Kurz gesagt, ThreadLocalObjekte funktionieren wie eine Schlüsselwertzuordnung. Wenn der Thread die ThreadLocal get/setMethode aufruft , werden das Thread-Objekt im Schlüssel der Karte und der Wert im Wert der Karte abgerufen / gespeichert. Aus diesem Grund haben verschiedene Threads unterschiedliche Wertkopien (die Sie lokal speichern möchten), da sie sich in verschiedenen Karteneinträgen befinden.

Deshalb benötigen Sie nur eine Karte, um alle Werte beizubehalten. Obwohl dies nicht erforderlich ist, können Sie mehrere Zuordnungen (ohne statische Deklaration) verwenden, um auch jedes Thread-Objekt beizubehalten. Dies ist völlig redundant. Aus diesem Grund wird eine statische Variable bevorzugt.

GMsoF
quelle
0

static final ThreadLocal Variablen sind threadsicher.

staticstellt die ThreadLocal-Variable über mehrere Klassen hinweg nur für den jeweiligen Thread zur Verfügung. Es ist eine Art Dekaration globaler Variablen der jeweiligen lokalen Thread-Variablen über mehrere Klassen hinweg.

Wir können die Sicherheit dieses Threads anhand des folgenden Codebeispiels überprüfen.

  • CurrentUser - speichert die aktuelle Benutzer-ID in ThreadLocal
  • TestService- Einfacher Dienst mit Methode - getUser()um den aktuellen Benutzer von CurrentUser abzurufen.
  • TestThread - Diese Klasse wird zum Erstellen mehrerer Threads und zum gleichzeitigen Festlegen von Benutzer-IDs verwendet

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

Führen Sie die TestThread-Hauptklasse aus. Ausgabe -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Analysezusammenfassung

  1. Der Thread "main" startet und setzt den aktuellen Benutzer auf "62". Der Thread "ForkJoinPool.commonPool-worker-2" startet und setzt den aktuellen Benutzer auf "31". Der Thread "ForkJoinPool.commonPool-worker-3" startet und setzt den aktuellen Thread Benutzer als "81", parallel "ForkJoinPool.commonPool-worker-1" Thread startet und aktuellen Benutzer als "87" Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. Alle oben genannten Threads werden 3 Sekunden lang in den Ruhezustand versetzt
  3. mainDie Ausführung endet und der aktuelle Benutzer wird als "62" ForkJoinPool.commonPool-worker-1gedruckt. Die parallele Ausführung endet und der aktuelle Benutzer wird als "87" ForkJoinPool.commonPool-worker-2gedruckt. Die parallele Ausführung endet und der aktuelle Benutzer wird als "31" ForkJoinPool.commonPool-worker-3gedruckt. Die parallele Ausführung endet und der aktuelle Benutzer wird als "81" gedruckt.

Inferenz

Concurrent Threads können korrekte Benutzer-IDs abrufen, selbst wenn sie als "statisch final ThreadLocal" deklariert wurden.

Shijin Raj
quelle