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 !