ThreadLocal est une classe utilitaire couramment utilisée du paquet java.lang du JDK, qui permet de gérer le contexte d’un thread. Son application la plus fréquente consiste sans doute à stocker des informations essentielles, comme le contexte de l’utilisateur après sa connexion.
Le contexte de thread peut également résoudre élégamment de nombreux problèmes qui semblent complexes, par exemple l’intégration des frameworks spring et mybatis :
Lors d’un développement dans l’écosystème spring, nous confions généralement la gestion des transactions à spring. L’annotation transactional sert à configurer leur activation, leur niveau d’isolation et d’autres paramètres. Nous n’accédons pas non plus directement à la base de données via jdbc, mais au moyen du framework mybatis. spring et mybatis sont deux frameworks totalement indépendants ; pourtant, à l’usage, mybatis semble pouvoir récupérer et utiliser directement la connexion à la base de données ouverte par spring. Nous n’avons pratiquement rien à faire : il suffit d’ajouter le fichier jar mybatis-spring-boot-stater.
La clé de cette conception élégante est ThreadLocal ; dans les grandes lignes, le fichier JAR stater finit par nous fournir un MybatisAutoConfiguration et l’injecter via SPI dans le conteneur de beans de notre service, dans MybatisAutoConfiguration a été configuré dans mybatis nécessaire SqlSessionFactory,et y a injecté celui de Spring lui-même SpringManagedTransactionFactory(a mis en œuvre TransactionFactory,la fabrique de transactions est un mécanisme d’extension fourni par mybatis, qui permet de déléguer la gestion des transactions).
Le déroulement global est le suivant : les méthodes portant l’annotation transactional sont finalement prises en charge par un proxy AOP. Dans le conseil exécuté avant l’appel, la classe proxy récupère une connection depuis dataSource, puis la place dans un ThreadLocal (en réalité, le ThreadLocal contient une map dont la clé est dataSource et la valeur connectionHolder, qui équivaut à connection). Ensuite, lorsque mybatis exécute le SQL, il commence par passer par TransactionFactory Obtenir la connexion, car à ce moment-là, ce qui est injecté TransactionFactory est SpringManagedTransactionFactory,la connection correspondant à la dataSource est donc récupérée dans ThreadLocal.
Présentation succincte du principe de ThreadLocal :
ThreadLocalMap est une table de hachage qui résout les collisions par sondage linéaire ; chaque thread en génère une, et chaque objet ThreadLocal contient un threadLocalHashCode Sert à calculer la clé de hachage.
Comparons avec HashMap,ThreadLocalMap Le choix du sondage linéaire comme algorithme de résolution des collisions plutôt que du chaînage peut reposer sur les considérations suivantes :
1、Considérations de mémoire : les usages étant différents, ThreadLocalMap contient généralement bien moins de données que HashMap. Avec peu de données, le chaînage entraîne un surcoût mémoire important pour les pointeurs
2、Efficacité : sur la base du point 1, lorsque le nombre d’éléments est faible, l’espace économisé peut servir à créer une table de hachage avec un facteur de charge plus faible, afin de réduire les collisions et d’améliorer la vitesse moyenne de recherche

Problème
A Dans les tâches fondées sur un pool de threads, la transmission du contexte pose problème, en particulier dans le modèle de programmation Web basé sur Tomcat. Comme Tomcat utilise lui-même un pool de threads pour traiter les échanges de requêtes et de réponses entre serveur et clients, chaque application Web doit gérer manuellement la bonne transmission du contexte des threads.
B Problème d’efficacité, si l’application contient trop de ThreadLocal, la table de hachage grossit et la vitesse de recherche diminue
Pour le problème A, les solutions courantes sont assez similaires ; voici directement le code de notre système
Cette solution présente deux problèmes :
1、Si de nouveaux ThreadLocal doivent ensuite être transmis correctement, il faut modifier le code pour les ajouter à la logique de wrap ; le moindre oubli provoquera un bug
2、Dans certains cas, la tâche peut être exécutée par le thread qui la soumet lui-même, par exemple :
Lorsque le pool de threads est saturé et que son
RejectedExecutionHandlerutiliseCallerRunsPolicy, les tâches soumises au pool de threads s’exécutent directement dans le thread qui les soumet ; la logique finale de nettoyage entraîne alors la perte du contexte de ce threadDe même, utiliser
ForkJoinPool(comprend l’exécution parallèleStreametCompletableFuture,utilise en interneForkJoinPool), le développement deForkJoinTasksera exécuté directement dans le thread qui soumet la tâche. Le nettoyage final entraînera lui aussi la perte du contexte de ce thread
Solution:
Problème 1 : si la classe ThreadLocal proposait une API permettant de connaître le nombre total d’objets ThreadLocal présents dans l’application, le problème serait résolu. Malheureusement, elle n’en propose pas. Pour forcer les choses, on peut utiliser la réflexion pour récupérer, dans l’objet thread, inheritableThreadLocals(c’est simplement un ThreadLocalMap), puis parcourir la table de hachage pour en extraire toutes les entrées permet d’obtenir toutes les paires clé-valeur threadlocal. Une autre approche consiste à ajouter une couche d’encapsulation au ThreadLocal du JDK pour y intégrer les fonctionnalités décrites ci-dessus.
Problème 2 : on peut le résoudre en améliorant la logique de wrap : au début de l’exécution du thread, récupérer son contexte actuel et le sauvegarder temporairement, puis le remplacer par celui du thread ayant soumis la tâche. Avant la fin de l’exécution, au lieu d’effacer le contexte, restaurer celui du thread à partir des données sauvegardées.
transmittable-thread-local
Des solutions toutes faites existent déjà ; leur principe consiste à agir selon les deux axes ci-dessus, et ce processus a reçu un nom CRR(capture/replay/restore) mode。
Il existe trois façons de l’utiliser :
1 ### Modification Runnable et Callable
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父线程中设置
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
// 额外的处理,生成修饰了的对象ttl
Runnable Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);
// =====================================================
// Task中可以读取,值是"value-set-in-parent"
String value = context.get();2 Décorer le pool de threads
ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象
executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// =====================================================
// 在父线程中设置
context.set("value-set-in-parent");
Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);
// =====================================================
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();3 ### Utilisation Java Agent pour qualifier JDK Classe d’implémentation du pool de threads
// ## 1. 框架上层逻辑,后续流程框架调用业务 ##
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");
// ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ##
ExecutorService executorService = Executors.newFixedThreadPool(3);
Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);
// ## 3. 框架下层逻辑 ##
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();L’avantage de l’approche java agent est qu’elle ne nécessite aucune modification du code de l’application. Quant à son inconvénient, le plus gros problème, à mon avis, est qu’oublier d’ajouter les paramètres au démarrage de la JVM peut provoquer un incident.
Examinons brièvement la logique du code source
Concernant le problème B, on ne s’en préoccupe généralement pas dans le développement d’applications, car la sensibilité au temps d’exécution n’y est pas aussi élevée. En revanche, dans le développement de certains intergiciels de bas niveau, l’optimisation des performances peut revêtir davantage d’importance. Par exemple, netty a choisi d’implémenter lui-même FastThreadLocal remplace ThreadLocal du JDK, tout en fournissant les éléments associés de FastThreadLocalThread,dans FastThreadLocalThread et FastThreadLocal Lorsqu’ils sont utilisés ensemble, on peut toujours garantir une complexité temporelle de recherche en O(1). Le principe de cette solution consiste à échanger de l’espace contre du temps. Pour éviter que les collisions de hachage dans la table ne réduisent l’efficacité des recherches, un tableau suffisamment long est créé, chaque FastThreadLocal Lors de la construction, un indice de tableau unique est attribué à chacun, comme une place réservée à chaque personne ; lors de la consultation, il suffit d’utiliser son propre index pour récupérer directement la valeur dans le tableau.
Voici brièvement le code source