Eine Einführung in ThreadLocal in Java

Eine Einführung in ThreadLocal in Java

1. Überblick

In diesem Artikel betrachten wir das KonstruktThreadLocalaus dem Paketjava.lang. Dies gibt uns die Möglichkeit, Daten für den aktuellen Thread einzeln zu speichern - und sie einfach in einen bestimmten Objekttyp zu packen.

2. ThreadLocal API

Das KonstruktTheadLocal ermöglicht es uns, Daten zu speichern, dieaccessible only mala specific thread sind.

Angenommen, wir möchten einenInteger-Wert haben, der mit dem spezifischen Thread gebündelt wird:

ThreadLocal threadLocalValue = new ThreadLocal<>();

Wenn wir diesen Wert aus einem Thread verwenden möchten, müssen wir nur die Methodeget() oderset() aufrufen. Einfach ausgedrückt können wir denken, dassThreadLocal Daten in einer Karte speichert - mit dem Thread als Schlüssel.

Aus diesem Grund erhalten wir beim Aufrufen einerget()-Methode fürthreadLocalValue einenInteger-Wert für den anfordernden Thread:

threadLocalValue.set(1);
Integer result = threadLocalValue.get();

Wir können eine Instanz derThreadLocal erstellen, indem wir die statische MethodewithInitial() verwenden und einen Lieferanten an sie übergeben:

ThreadLocal threadLocal = ThreadLocal.withInitial(() -> 1);

Um den Wert ausThreadLocal zu entfernen, können wir eineremove()-Methode aufrufen:

threadLocal.remove();

Um zu sehen, wie dieThreadLocal richtig verwendet werden, sehen wir uns zunächst ein Beispiel an, in dem keineThreadLocal verwendet werden, und schreiben dann unser Beispiel neu, um dieses Konstrukt zu nutzen.

3. Speichern von Benutzerdaten in einer Karte

Betrachten wir ein Programm, das die benutzerspezifischenContext-Daten pro gegebener Benutzer-ID speichern muss:

public class Context {
    private String userName;

    public Context(String userName) {
        this.userName = userName;
    }
}

Wir möchten einen Thread pro Benutzer-ID haben. Wir erstellen eineSharedMapWithUserContext-Klasse, die eineRunnable-Schnittstelle implementiert. Die Implementierung in derrun()-Methode ruft eine Datenbank über dieUserRepository-Klasse auf, die einContext-Objekt für ein bestimmtesuserId zurückgibt.

Als nächstes speichern wir diesen Kontext inConcurentHashMap, die mituserId verschlüsselt sind:

public class SharedMapWithUserContext implements Runnable {

    public static Map userContextPerUserId
      = new ConcurrentHashMap<>();
    private Integer userId;
    private UserRepository userRepository = new UserRepository();

    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContextPerUserId.put(userId, new Context(userName));
    }

    // standard constructor
}

Wir können unseren Code einfach testen, indem wir zwei Threads für zwei verschiedeneuserIdserstellen und starten und bestätigen, dass wir zwei Einträge in deruserContextPerUserId-Karte haben:

SharedMapWithUserContext firstUser = new SharedMapWithUserContext(1);
SharedMapWithUserContext secondUser = new SharedMapWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();

assertEquals(SharedMapWithUserContext.userContextPerUserId.size(), 2);

4. Speichern von Benutzerdaten inThreadLocal

Wir können unser Beispiel neu schreiben, um die Instanz des BenutzersContextmitThreadLocal zu speichern. Jeder Thread hat eine eigeneThreadLocal-Instanz.

Bei der Verwendung vonThreadLocal müssen wir sehr vorsichtig sein, da jedeThreadLocal-Instanz einem bestimmten Thread zugeordnet ist. In unserem Beispiel haben wir einen dedizierten Thread für jeden bestimmtenuserId und dieser Thread wird von uns erstellt, damit wir die volle Kontrolle darüber haben.

Die Methoderun() ruft den Benutzerkontext ab und speichert ihn mit der Methodeset() in der VariablenThreadLocal:

public class ThreadLocalWithUserContext implements Runnable {

    private static ThreadLocal userContext
      = new ThreadLocal<>();
    private Integer userId;
    private UserRepository userRepository = new UserRepository();

    @Override
    public void run() {
        String userName = userRepository.getUserNameForUserId(userId);
        userContext.set(new Context(userName));
        System.out.println("thread context for given userId: "
          + userId + " is: " + userContext.get());
    }

    // standard constructor
}

Wir können es testen, indem wir zwei Threads starten, die die Aktion für ein bestimmtesuserIdausführen:

ThreadLocalWithUserContext firstUser
  = new ThreadLocalWithUserContext(1);
ThreadLocalWithUserContext secondUser
  = new ThreadLocalWithUserContext(2);
new Thread(firstUser).start();
new Thread(secondUser).start();

Nach dem Ausführen dieses Codes sehen wir in der Standardausgabe, dassThreadLocal für einen bestimmten Thread festgelegt wurde:

thread context for given userId: 1 is: Context{userNameSecret='18a78f8e-24d2-4abf-91d6-79eaa198123f'}
thread context for given userId: 2 is: Context{userNameSecret='e19f6a0a-253e-423e-8b2b-bca1f471ae5c'}

Wir können sehen, dass jeder Benutzer seinen eigenen Kontext hat.

5. Verwenden SieThreadLocal nicht mitExecutorService

Wenn wir einExecutorService verwenden und einRunnable an dieses senden möchten, führt die Verwendung vonThreadLocal zu nicht deterministischen Ergebnissen - da wir nicht garantieren können, dass jedeRunnable-Aktion ausgeführt wird für ein bestimmtesuserIdwird s jedes Mal, wenn es ausgeführt wird, von demselben Thread behandelt.

Aus diesem Grund werden unsereThreadLocal auf verschiedeneuserIds. aufgeteilt. Deshalb sollten wirTheadLocal nicht zusammen mitExecutorService. verwenden. Es sollte nur verwendet werden, wenn wir die volle Kontrolle darüber haben Welcher Thread wählt welche ausführbare Aktion aus, die ausgeführt werden soll?

6. Fazit

In diesem kurzen Artikel haben wir uns das KonstruktThreadLocalangesehen. Wir haben die Logik implementiert, dieConcurrentHashMap verwendet, die von Threads gemeinsam genutzt wurden, um den mit einem bestimmtenuserId. verknüpften Kontext zu speichern. Als Nächstes haben wir unser Beispiel neu geschrieben, umThreadLocal zum Speichern von Daten zu nutzen, die mit a verknüpft sind bestimmteuserId und mit einem bestimmten Thread.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.