Cet article aide tout le monde à comprendre le threadlocal en utilisant des cas d'optimisation simpletateFormat qui sont dangereux dans des environnements simultanés.
Récemment, j'ai réglé les projets de l'entreprise et j'ai constaté qu'il y avait beaucoup de mauvaises choses écrites, comme les suivantes:
classe publique DateUtil {private final static SimpledateFormat sdfyhm = new SimpledateFormat ("yyyymmdd"); Date statique synchronisée publique ParseyMDHMS (chaîne Source) {try {return sdfyhm.parse (source); } catch (parseException e) {e.printStackTrace (); retourner la nouvelle date (); }}} Tout d'abord, analysons:
La fonction ParseyMDhms () à ce stade utilise une modification synchronisée, ce qui signifie que l'opération est un thread-unsafe, il doit donc être synchronisé. Le thread-unsafe ne peut être que la méthode Parse () de SimpledateFormat. Vérifiez le code source. Il existe une variable globale dans SimpledateFormat.
Calendrier de calendrier protégé; Date Parse () {Calendar.Clear (); ... // effectuer certaines opérations pour définir la date de calendrier et autres calendar.getTime (); // Obtenez l'heure du calendrier}L'opération Clear () provoquera un thread insécurisé.
De plus, l'utilisation du mot-clé synchronisé a un grand impact sur les performances, en particulier lors du multi-threading, chaque fois que la méthode ParseyMDHMS est appelée, le jugement de synchronisation sera fait et la synchronisation elle-même est très coûteuse, donc il s'agit d'une solution déraisonnable.
Méthode d'amélioration
Le thread insécurisé est causé par l'utilisation de variables partagées par plusieurs threads, nous utilisons donc ici ThreadLocal <ImpledateFormat> pour créer une variable de copie pour chaque thread séparément. Donnez d'abord le code, puis analysez la cause de ce problème pour résoudre le problème.
/ ** * Classe d'outils de date (ThreadLocal est utilisé pour devenir simpledateFormat, et d'autres méthodes peuvent être copiées directement par Common-Lang) * @author niu li * @date 2016/11/19 * / classe publique DateUtil {Map statique privée <String, ThreadLocal <SimpledateFormat>> sdfmap = new hashmap <String, ThreadLocal <simpleatefmap>;); Logger statique privé = loggerfactory.getLogger (dateUtil.class); chaîne statique finale publique mdhmss = "MMDDHHMMSSSSS"; chaîne statique finale publique ymdhms = "yyymmddhhmmss"; Public Final Static String ymdhms_ = "Yyyy-mm-dd hh: mm: ss"; Public Final Static String ymd = "yyyymmdd"; Public Final Static String ymd_ = "Yyyy-mm-dd"; Public Final Static String HMS = "HHMMSS"; / ** * Obtenez l'instance SDF du thread correspondant en fonction de la touche de la touche MAP * @param du modèle dans la carte * @return cette instance * / private static SimpledateFormat GetSDF (Final String Match) {ThreadLocal <SimpledateFormat> sdfThread = sdfmap.get (modèle); if (sdfThread == null) {// double vérifier pour empêcher SDFMAP de mettre la valeur en plusieurs fois, et la raison du singleton à double verrouillage est la même que Synchronized (dateUtil.class) {sdfThread = sdfmap.get (modèle); if (sdfThread == null) {logger.debug ("mettre un nouveau SDF de modèle" + motif + "à map"); sdfThread = new ThreadLocal <SimpledateFormat> () {@Override Protected SimpledateFormat initialValue () {Logger.debug ("Thread:" + Thread.currentThread () + "INIT Pattern:" + Pattern); return new SimpledateFormat (modèle); }}; sdfmap.put (modèle, sdfThread); }}} return sdfThread.get (); } / ** * Analyse de la date en fonction du modèle spécifié * @param Date La date à analyser * @Param Date Modèle Spécifiez le format * @return Anared Date Instance * / Public Static Date Parsedate (Date de chaîne, modèle de chaîne) {If (date == NULL) {Throw New IllégalArgumentException ("La date ne doit pas être null"); } essayez {return getSDf (modèle) .Parse (date); } catch (parseException e) {e.printStackTrace (); Logger.Error ("Le format analysé ne prend pas en charge:" + modèle); } return null; } / ** * Date de format Selon le modèle spécifié * @param Date La date à formater * @Param Pattern Spécifiez le format * @return Panned Format * / Public Static String Formatdate (date de date, modèle de chaîne) {if (date == Null); } else {return getSDf (modèle) .format (date); }}}test
En exécuter un dans le thread principal et les deux autres dans le thread enfant, tous deux en utilisant le même motif
public static void main (String [] args) {dateUtil.formatDate (new Date (), mdhmss); nouveau thread (() -> {dateUtil.formatDate (new Date (), mdhmss);}). start (); nouveau thread (() -> {dateUtil.formatDate (new Date (), mdhmss);}). start (); }Analyse des journaux
Mettez un nouveau SDF de motif MMDDHHMMSSSSS sur MapThread: Thread [Main, 5, Main] Modèle d'initiation: MMDDHHMMSSSSTHREAD: Thread [Thread-0,5, Main] Modèle init: MMDDHHMMSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS
analyser
On peut voir que SDFMAP a été entré une fois, tandis que SimpledateFormat était nouveau trois fois car il y avait trois threads dans le code. Alors pourquoi est-ce?
Pour chaque thread, il existe une référence de variable globale à ThreadLocal.ThreadLocalmap ThreadLocals. Il existe un threadlocal.threadlocalmap qui contient la valeur threadlocal et la valeur correspondante. Une image vaut mieux que mille mots. Le diagramme de structure est le suivant:
Donc pour SDFMAP, le diagramme de structure sera modifié
1. Exécuter d'abord dateUtil.formatDate (new Date (), MDHMSS);
// La première fois que j'ai exécuté dateUtil.formatDate (new Date (), MDHMSS) Analyse private static SimpledateFormat GetSDF (Final String Match) {ThreadLocal <ImpledateFormat> sdfThread = sdfmap.get (modèle); // Le sdfThread obtenu est null, entrez si instruction if (sdfThread == null) {synchronisé (dateUtil.class) {sdfThread = sdfmap.get (modèle); // sdfThread est toujours nul, entrez si instruction if (sdfThread == null) {// imprimer logger.debug ("mettre un nouveau SDF de modèle" + motif + "à map"); // Créer une instance threadLocal et remplacer la méthode initiale Value sdfThread = new ThreadLocal <SimpledateFormat> () {@Override Protected SimpledateFormat initialValue () {Logger.DeBug ("Thread:" + Thread.currentThread () + "Init Match:" + Match); return new SimpledateFormat (modèle); }}; // Définir dans sdfmap sdfmap.put (motif, sdfThread); }}} return sdfThread.get (); } À l'heure actuelle, quelqu'un peut demander, la méthode de jeu threadlocal n'est pas appelée ici, alors comment définissez-vous la valeur pour entrer?
Cela nécessite la mise en œuvre de sdfThread.get ():
public t get () {thread t = thread.currentThread (); ThreadLocalmap map = getmap (t); if (map! = null) {threadLocalmap.entry e = map.getEntry (this); if (e! = null) {@SuppressWarnings ("Unchecked") t result = (t) e.Value; Résultat de retour; }} return setInitialValue (); }C'est-à-dire que lorsque la valeur n'existe pas, la méthode setInitialValue () sera appelée, qui appellera la méthode initialValue (), qui est la méthode que nous remplacez.
Impression logarithmique correspondante.
Mettez un nouveau SDF de modèle MMDDHHMMSSSSS sur MapThread: Thread [Main, 5, Main] Modèle d'initial: MMDDHHMMSSSSSS
2. Exécuter DateUtil.formatDate (new Date (), MDHMSS) sur le thread de l'enfant la deuxième fois;
// Exécuter `dateUtil.formatDate (new Date (), MDHMSS);` Private Static SimpledateFormat GetSDF (Final String Match) {ThreadLocal <ImpledateFormat> sdfThread = sdfmap.get (modèle); // Le sdfThread obtenu ici n'est pas nul, sauter si block if (sdfThread == null) {synchronisé (dateUtil.class) {sdfThread = sdfmap.get (modèle); if (sdfThread == null) {logger.debug ("mettre un nouveau SDF de modèle" + motif + "à map"); sdfThread = new ThreadLocal <SimpledateFormat> () {@Override Protected SimpledateFormat initialValue () {Logger.debug ("Thread:" + Thread.currentThread () + "INIT Pattern:" + Pattern); return new SimpledateFormat (modèle); }}; sdfmap.put (modèle, sdfThread); }}} // Appel sdfThread.get () directement pour retourner sdfThread.get (); }Analyse sdfthread.get ()
// Exécuter `dateUtil.formatDate (new Date (), mdhmss);` public t get () {thread t = thread.currentThread (); // obtient le thread enfant actuel threadLocalmap map = getMap (t); // La carte obtenue dans le thread enfant est nul, sautez si le bloc if (map! = Null) {threadLocalmap.entry e = map.getEntry (this); if (e! = null) {@SuppressWarnings ("Unchecked") t result = (t) e.Value; Résultat de retour; }} // Exécuter directement l'initialisation, c'est-à-dire, appelez la méthode initialValue () que nous écrasons le retour setInitialValue (); }Journal correspondant:
Thread [Thread-1,5, Main] Modèle init: MMDDHHMMSSSSSS
Résumer
Dans quel scénario est le plus adapté à l'utilisation de threadlocal? Quelqu'un a donné une assez bonne réponse sur Stackoverflow.
Quand et comment dois-je utiliser une variable threadlocal?
Une utilisation possible (et commune) est lorsque vous avez un objet qui n'est pas en file d'attente, mais vous voulez éviter de synchroniser l'accès à cet objet (je vous regarde, simpledateFormat). Au lieu de cela, donnez à chaque thread sa propre instance de l'objet.
Code de référence:
https://github.com/nl101531/util-demo sous Javaweb
Références:
Apprenez Java Threadlocal d'une manière facile à comprendre
Problème et solution de sécurité de threads simpletateFormat
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.