Dieser Artikel hilft jedem, ThreadLocal zu verstehen, indem sie in gleichzeitigen Umgebungen nicht sicher sind.
Vor kurzem habe ich die Projekte des Unternehmens ausgelehrt und festgestellt, dass viele schlechte Dinge geschrieben sind, wie die folgenden:
öffentliche Klasse DateUtil {private endgültige statische SimpleDateFormat sdfyhm = new SimpledateFormat ("yyyymmdd"); öffentliches synchronisiertes statisches Datum Parseymdhms (String Source) {try {return sdfyHm.Parse (Quelle); } catch (parseException e) {e.printstacktrace (); Neues Datum zurückgeben (); }}} Lassen Sie uns zunächst analysieren:
Die Funktion parseymdhmms () verwendet an diesem Punkt eine synchronisierte Modifikation, was bedeutet, dass der Operation Thread-Unsafe ist, sodass sie synchronisiert werden muss. Der Thread-Unsafe kann nur die Parse () -Methode von SimpleDateFormat sein. Überprüfen Sie den Quellcode. In SimpleDateFormat gibt es eine globale Variable.
geschützter Kalenderkalender; Datum Parse () {Calendar.Clear (); ... // einige Operationen durchführen, um das Datum des Kalenders und anderer Kalender festzulegen.getTime (); // Erhalten Sie die Zeit des Kalenders}Die Operation Clear () führt zu Unsicherheit.
Die Verwendung des synchronisierten Keywords hat einen großen Einfluss auf die Leistung, insbesondere wenn Multi-Threading jedes Mal, wenn die Parseymdhms-Methode aufgerufen wird, das Synchronisationsurteil erfolgt und die Synchronisation selbst sehr teuer ist, daher ist dies eine unvernünftige Lösung.
Verbesserungsmethode
Thread -Unsicherheit wird durch die Verwendung gemeinsamer Variablen nach mehreren Threads verursacht. Hier verwenden wir hier ThreadLocal <fungedateFormat>, um eine Kopiervariable für jeden Thread separat zu erstellen. Geben Sie zuerst den Code an und analysieren Sie dann die Ursache dieses Problems, um das Problem zu lösen.
/** * Datum der Werkzeugklasse (ThreadLocal wird verwendet, um SimpledateFormat zu erhalten, und andere Methoden können direkt von Common-Lang kopiert werden) * @Author niu li * @date 2016/11/19 */public class DateUtil {private static map <String, ThreadLocal <SimpleDateFormat >> Sdfmap = New Hashmap <String, ThreadLocal <SimpleDateFormat >> () (); private static logger logger = loggerfactory.getLogger (DateUtil.Class); öffentliches endgültiges statisches String mdhmss = "mmddhhmmssssss"; öffentliche endgültige statische String ymdhmms = "yyymmddhhmms"; öffentliche endgültige statische String ymdhmms_ = "yyyy-mm-dd hh: mm: ss"; öffentliche endgültige statische String ymd = "yyyymmdd"; öffentliche endgültige statische String ymd_ = "yyyy-mm-dd"; öffentliche endgültige statische Zeichenfolge HMS = "HHMMSS"; / *** Holen Sie sich die SDF -Instanz des entsprechenden Threads basierend auf der Taste in der Karte* @param Muster -Taste in der Karte* @return diese Instanz*/ privat static SimpleDateFormat getDf (endgültiges Zeichenfolgenmuster) {ThreadLocal <fimpedateFormat> sdfThread = sdfmap.get (Muster); if (sdfThread == null) {// double Check, um zu verhindern, dass SDFMAP den Wert mehrmals in den Wert des Werts einfügt, ist der Grund für das Double Lock Singleton wie synchronisiert (DateUtil.class) {SdfThread = sdfmap.get (Muster); if (sdfThread == null) {logger.debug ("Neues SDF des Musters setzen" + Muster + "zu map"); sdfThread = new ThreadLocal <fimpledateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug ("Thread:" + thread.currentThread () + "init Muster:" + Muster); Neue SimpledateFormat (Muster) zurückgeben; }}; sdfmap.put (muster, sdfThread); }}} return sdfThread.get (); } / *** das Datum nach dem angegebenen Muster analysieren* @Param Datum Das zu analysierte Datum* @param Datum Muster angeben. } try {return getDf (Muster) .Parse (Datum); } catch (parseException e) {e.printstacktrace (); Logger.Error ("Das analysierte Format unterstützt nicht:"+Muster); } return null; } / *** Formatdatum gemäß dem angegebenen Muster* @Param Datum Das zu formatierte Datum* @param Muster Geben Sie das Format an* @return Parsed -Format* / public static String FormatDate (Datum Datum, String -Muster) {if (Datum == null) {Neue IlalArgumentException ("Das Datum darf Null nicht sein"); } else {return getDf (Muster) .Format (Datum); }}}prüfen
Führen Sie einen im Haupt -Thread und die beiden anderen im untergeordneten Thread aus, beide verwenden das gleiche Muster
public static void main (String [] args) {dateUtil.formatdate (neues Datum (), mdhmss); neuer Thread (()-> {dateUtil.formatdate (neuer Date (), MdHMSS);}). start (); neuer Thread (()-> {dateUtil.formatdate (neuer Date (), MdHMSS);}). start (); }Protokollanalyse
Setzen Sie neue SDF Muster MMDDHHMMMSSSS in MapThread: Thread [Main, 5, Haupt] Init Muster: Mmddhhmmmsssssthread: Thread [Thread-0,5, Haupt] Init-Muster: Mmddhhmmmssshread: Thread-1, Main] init: mmdhhmmmsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSs
analysieren
Es ist ersichtlich, dass SDFMAP ein einmal eingegeben wurde, während SimpledateFormat dreimal neu war, da es drei Themen im Code befand. Warum ist das?
Für jeden Thread -Thread gibt es eine globale Variablenreferenz auf ThreadLocal.ThreadLocalMap Threadlocals. Es gibt eine ThreadLocal.ThreadLocalMap, die den ThreadLocal und den entsprechenden Wert hält. Ein Bild ist besser als tausend Worte. Das Strukturdiagramm lautet wie folgt:
Für SDFMAP wird das Strukturdiagramm also geändert
1. Führen Sie zuerst DateUtil.formatdate (neues Datum (), MDHMSS) aus;
// Das erste Mal, dass ich DateUtil.formatdate (New Date (), MDHMSS) Analyse private static SimpleDateFormat getdf (endgültiges Zeichenfolgenmuster) {ThreadLocal <flojedateFormat> SdfThread = Sdfmap.get (Muster); // Das erhaltene SDFThread ist null. Geben Sie eine Anweisung ein, wenn (sdfThread == null) {synchronized (dateUtil.class) {sdfThread = sdfmap.get (Muster); // sdfThread ist immer noch null. Geben Sie eine Anweisung ein, wenn (sdfThread == null) {// logger.debug drucken ("Neue SDF -Muster einlegen" + Muster + "in die Karte"); // ThreadLocal -Instanz erstellen und überschreiben initialValue -Methode sdfThread = new ThreadLocal <fungedateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug ("Thread:" + Thread.Current thread () + Init: " + Muster); Neue SimpledateFormat (Muster) zurückgeben; }}; // in sdfmap sdfmap.put (Muster, SDFThread) einstellen; }}} return sdfThread.get (); } Zu diesem Zeitpunkt kann jemand fragen, dass die ThreadLocal -Set -Methode hier nicht aufgerufen wird. Wie setzen Sie also den zugänglichsten Wert fest?
Dies erfordert die Implementierung von sdfThread.get ():
public t get () {Thread t = thread.currentThread (); ThreadLocalMap map = getMap (t); if (map! if (e! Rückgabeergebnis; }} return setInitialValue (); }Das heißt, wenn der Wert nicht vorhanden ist, wird die Methode setInitialValue () aufgerufen, die die Methode von InitialValue () aufgerufen wird, die die Methode ist, die wir überschreiben.
Entsprechender Protokolldruck.
Setzen Sie neue SDF Muster MMDDHHMMMSSSS in MapThread: Thread [Main, 5, Main] Init Muster: MMDDHHMMMSSSSSSSSSSSS
2. Führen Sie das zweite Mal auf dem untergeordneten Thread auf dem untergeordneten Thread aus.
// `dateUtil.formatdate (new Date (), mdhmss);` private static SimpleDateFormat getdf (endgültiges Zeichenfolgenmuster) {ThreadLocal <flojedateFormat> sdfThread = sdfmap.get (Muster); // Das hier erhaltene SDFThread ist nicht null. Überspringen Sie, wenn (sdfThread == null) {synchronized (dateUtil.class) {sdfThread = sdfmap.get (Muster); if (sdfThread == null) {logger.debug ("Neues SDF des Musters setzen" + Muster + "zu map"); sdfThread = new ThreadLocal <fimpledateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug ("Thread:" + thread.currentThread () + "init Muster:" + Muster); Neue SimpledateFormat (Muster) zurückgeben; }}; sdfmap.put (muster, sdfThread); }}} // rufen Sie sdfThread.get () direkt auf, um sdfThread.get () zurückzugeben; }Analyse sdfThread.get ()
// `DateUtil.FormatDate (New Date (), MdHMSS);` public t get () {Thread t = thread.currentThread (); // Die aktuelle untergeordnete Thread -ThreadLocalMap -MAP = getMap (t) abrufen; // Die im untergeordneten Thread erhaltene Karte ist null. Überspringen Sie, wenn (map! = Null) {threadLocalMap.Entry e = map.getEntry (this); if (e! Rückgabeergebnis; }} // Die Initialisierung direkt ausführen, dh auf die initialValue () -Methode, die wir return setInitialValue () überschreiben; }Entsprechendes Protokoll:
Thread [Thread-1,5, Haupt] Init-Muster: MMDDHHMMMSSSSSSS
Zusammenfassen
In welchem Szenario eignet sich besser für die Verwendung von ThreadLocal? Jemand gab eine ziemlich gute Antwort auf Stackoverflow.
Wann und wie soll ich eine Threadlokalvariable verwenden?
Eine mögliche (und übliche) Verwendung ist, wenn Sie über ein Objekt verfügen, das nicht mit Thread-Sicherheit ist, aber Sie möchten vermeiden, den Zugriff auf dieses Objekt zu synchronisieren (ich schaue Sie an, SimpleDateFormat). Geben Sie stattdessen jedem Thread eine eigene Instanz des Objekts.
Referenzcode:
https://github.com/nl101531/util-demo unter javaweb
Referenzen:
Lernen Sie Java ThreadLocal auf leicht verständliche Weise
Das Problem und die Lösung von SimpleDateFormat von Threadates
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, es wird für das Lernen aller hilfreich sein und ich hoffe, jeder wird Wulin.com mehr unterstützen.