Une introduction à ThreadLocal en Java

Une introduction à ThreadLocal en Java

1. Vue d'ensemble

Dans cet article, nous examinerons la constructionThreadLocal du packagejava.lang. Cela nous donne la possibilité de stocker des données individuellement pour le thread actuel - et de les envelopper simplement dans un type d'objet spécial.

2. APIThreadLocal

La constructionTheadLocal nous permet de stocker des données qui serontaccessible only para specific thread.

Disons que nous voulons avoir une valeurInteger qui sera regroupée avec le thread spécifique:

ThreadLocal threadLocalValue = new ThreadLocal<>();

Ensuite, lorsque nous voulons utiliser cette valeur à partir d'un thread, nous devons seulement appeler une méthodeget() ouset(). En termes simples, nous pouvons penser queThreadLocal stocke des données à l'intérieur d'une carte - avec le thread comme clé.

De ce fait, lorsque nous appelons une méthodeget() sur lethreadLocalValue, nous obtiendrons une valeurInteger pour le thread demandeur:

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

Nous pouvons construire une instance desThreadLocal en utilisant la méthode statique dewithInitial() et en lui passant un fournisseur:

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

Pour supprimer la valeur desThreadLocal, nous pouvons appeler une méthoderemove():

threadLocal.remove();

Pour voir comment utiliser correctement lesThreadLocal, tout d'abord, nous allons regarder un exemple qui n'utilise pas deThreadLocal, puis nous réécrirons notre exemple pour tirer parti de cette construction.

3. Stockage des données utilisateur dans une carte

Considérons un programme qui a besoin de stocker les donnéesContext spécifiques à l'utilisateur par ID utilisateur donné:

public class Context {
    private String userName;

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

Nous voulons avoir un thread par identifiant d'utilisateur. Nous allons créer une classeSharedMapWithUserContext qui implémente une interfaceRunnable. L'implémentation dans la méthoderun() appelle une base de données via la classeUserRepository qui renvoie un objetContext pour unuserId donné.

Ensuite, nous stockons ce contexte dans lesConcurentHashMap saisis paruserId:

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
}

Nous pouvons facilement tester notre code en créant et en démarrant deux threads pour deuxuserIds différents et en affirmant que nous avons deux entrées dans la carteuserContextPerUserId:

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

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

4. Stockage des données utilisateur enThreadLocal

Nous pouvons réécrire notre exemple pour stocker l'instance de l'utilisateurContext en utilisant unThreadLocal. Chaque thread aura sa propre instanceThreadLocal.

Lorsque vous utilisezThreadLocal, nous devons être très prudents car chaque instance deThreadLocal est associée à un thread particulier. Dans notre exemple, nous avons un thread dédié pour chaqueuserId particulier et ce thread est créé par nous afin que nous ayons un contrôle total dessus.

La méthoderun() récupérera le contexte utilisateur et le stockera dans la variableThreadLocal en utilisant la méthodeset():

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
}

Nous pouvons le tester en démarrant deux threads qui exécuteront l'action pour unuserId donné:

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

Après avoir exécuté ce code, nous verrons sur la sortie standard queThreadLocal a été défini pour un thread donné:

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'}

Nous pouvons voir que chacun des utilisateurs a son propre contexte.

5. Ne pas utiliserThreadLocal avecExecutorService

Si nous voulons utiliser unExecutorService et lui soumettre unRunnable, utiliserThreadLocal donnera des résultats non déterministes - car nous n'avons pas la garantie que chaque action deRunnable pour unuserIddonné sera traité par le même thread à chaque fois qu'il est exécuté.

Pour cette raison, nosThreadLocal seront partagés entre différentsuserIds. C'est pourquoi nous ne devrions pas utiliser unTheadLocal avecExecutorService. Il ne devrait être utilisé que lorsque nous avons un contrôle total sur quel thread sélectionnera l'action exécutable à exécuter.

6. Conclusion

Dans cet article rapide, nous avons examiné la constructionThreadLocal. Nous avons implémenté la logique qui utiliseConcurrentHashMap qui était partagé entre les threads pour stocker le contexte associé à unuserId. particulier Ensuite, nous avons réécrit notre exemple pour tirer parti deThreadLocal pour stocker des données associées à un particulieruserId et avec un thread particulier.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans leGitHub project - il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.