Introduction
La classe Java ThreadLocal
nous permet de créer des variables qui ne peuvent être lues et écrites que par le même thread. Ainsi, même si deux threads exécutent le même code et que ce code fait référence à la même variable ThreadLocal
, les deux threads ne peuvent pas voir les variables ThreadLocal
de l'autre, ce qui est un moyen simple de rendre le code sûr pour les threads. Les instances ThreadLocal sont généralement des champs privés statiques dans les classes qui souhaitent associer un état à un thread (par exemple, l'ID de l'utilisateur, l'ID de la transaction, etc.)
private static ThreadLocal<Integer> userIdThreadLocal = new ThreadLocal<>();
Ensuite, lorsque nous voulons utiliser cette valeur à partir d'un thread, nous appelons simplement la méthode get()
ou set()
de ThreadLocal
. Vous pouvez imaginer que ThreadLocal
stocke les données dans une carte avec le thread comme clé. Par conséquent, lorsque nous appelons une méthode get()
sur userIdThreadLocal
, nous obtenons la valeur pour le thread demandeur :
userIdThreadLocal.set(1);
Integer user = userIdThreadLocal.get();
Méthodes de ThreadLocal
Méthode-signature
static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
Crée une variable locale pour le thread.
Méthode-signature
protected T initialValue()
Renvoie la "valeur initiale" de la discussion en cours pour cette variable locale.
Méthode-signature
T get()
Renvoie la valeur de la copie de cette variable locale pour le thread en cours.
Méthode-signature
void remove()
Supprime la valeur de la discussion en cours pour cette variable locale.
Méthode-signature
void set(T value)
Définit la copie de cette variable locale pour le thread en cours à la valeur spécifiée.
Valeur initiale de ThreadLocal
Il est possible de définir une valeur initiale pour un ThreadLocal
qui sera retournée la première fois que la méthode get()
est appelée, avant même que la méthode set()
ne soit appelée. Nous avons deux options pour spécifier une valeur initiale pour un ThreadLocal
:
**Surcharge de la méthode initialValue()
**.
La première façon de spécifier une valeur initiale pour une variable ThreadLocal
en Java est de créer une sous-classe de ThreadLocal
qui surcharge sa méthode initialValue()
. Voici un exemple qui crée une sous-classe anonyme de ThreadLocal
qui surcharge la méthode initialValue()
:
private ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return UUID.randomUUID().toString();
}
};
**Fournir une implémentation de l'interface Supplier
**.
La seconde méthode consiste à utiliser la méthode statique withInitial(Supplier)
de ThreadLocal
, en passant en paramètre une implémentation de l'interface Supplier
. Cette implémentation de Supplier
fournit la valeur initiale pour le ThreadLocal
. Voici un exemple de création d'un ThreadLocal
en utilisant sa méthode d'usine statique withInitial()
:
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
return UUID.randomUUID().toString();
}
});
ThreadLocals et Thread Pools
Nous devons être particulièrement prudents lorsque nous utilisons les ThreadLocals
et les pools de threads ensemble. La technique du pool de threads permet de réutiliser les threads afin de réduire la charge de travail liée à la création de threads. Chaque tâche qui entre dans le pool s'attend à voir les objets ThreadLocals
dans leur état initial, par défaut. Cependant, lorsque les objets ThreadLocal
sont modifiés sur un thread qui est ensuite rendu disponible pour la réutilisation, la prochaine tâche exécutée sur le thread réutilisé voit l'état des objets ThreadLocal
tel que modifié par la tâche précédente qui a été exécutée sur ce thread.
Les programmes doivent s'assurer que chaque tâche exécutée sur un thread dans un pool de threads ne voit que des instances correctement initialisées des objets ThreadLocal
. La bonne nouvelle est qu'il est possible d'étendre la classe ThreadPoolExecutor
et de fournir une implémentation personnalisée pour les méthodes beforeExecute()
et afterExecute()
. Nous pouvons donc étendre la classe ThreadPoolExecutor
et supprimer la variable ThreadLocal
de la méthode afterExecute()
:
public class ThreadLocalAwareThreadPoolExecutor extends ThreadPoolExecutor {
@Override
protected void afterExecute(Runnable r, Throwable t) {
// delete the variable from the current thread
}
}
InheritableThreadLocal
La classe InheritableThreadLocal
étend ThreadLocal
pour permettre l'héritage des valeurs du thread parent au thread enfant : lorsqu'un thread enfant est créé, il reçoit les valeurs initiales de toutes les variables thread-locales héritables pour lesquelles le parent a des valeurs. Normalement, les valeurs de l'enfant seront identiques à celles du parent.
Les variables thread-locales héritables sont utilisées de préférence aux variables thread-locales ordinaires lorsque l'attribut par thread maintenu dans la variable (par exemple, l'ID de l'utilisateur, l'ID de la transaction, ...) doit être automatiquement transmis à tous les threads enfants qui sont créés. Voici un exemple qui illustre les avantages de la classe InheritableThreadLocal
:
Utilisation de ThreadLocal
class ParentThread extends Thread {
public static ThreadLocal<String> parentThreadLocal = new ThreadLocal<>();
public void run() {
parentThreadLocal.set("données parent");
System.out.println("Valeur Parent ThreadLocal: " + parentThreadLocal.get());
new ChildThread().start();
}
}
class ChildThread extends Thread {
public void run() {
System.out.println("Valeur Child ThreadLocal: " + ParentThread.parentThreadLocal.get());
}
}
class ThreadLocalDemo {
public static void main(String[] args) {
new ParentThread().start();
}
}
Exécutez l'exemple ci-dessus et vous obtiendrez le résultat suivant :
sans numéros de ligne
Valeur Parent ThreadLocal: données parent
Valeur Child ThreadLocal: null
Utilisation de InheritableThreadLocal
terminal
class ParentThread extends Thread {
public static InheritableThreadLocal<String> parentThreadLocal = new InheritableThreadLocal<>();
public void run() {
parentThreadLocal.set("données parent");
System.out.println("Valeur Parent InheritableThreadLocal: " + parentThreadLocal.get());
new ChildThread().start();
}
}
class ChildThread extends Thread {
public void run() {
System.out.println("Valeur Child InheritableThreadLocal: " + ParentThread.parentThreadLocal.get());
}
}
class ThreadLocalDemo {
public static void main(String[] args) {
new ParentThread().start();
}
}
Exécutez l'exemple ci-dessus et vous obtiendrez le résultat suivant :
sans numéros de ligne
Valeur Parent InheritableThreadLocal: données parent
Valeur Child InheritableThreadLocal: données parent
Conclusion
Merci d'avoir lu cet article. Nous espérons que cet article vous a été utile !