تساعد هذه المقالة الجميع على فهم Threadlocal باستخدام حالات تحسين SimpledAteFormat غير الآمنة في البيئات المتزامنة.
في الآونة الأخيرة ، قمت بفرز مشاريع الشركة ووجدت أن هناك العديد من الأشياء السيئة المكتوبة ، مثل ما يلي:
الفئة العامة DateUtil {Private Final Static SimpledateFormat sdfyhm = new SimpleDateFormat ("Yyyymmdd") ؛ تاريخ ثابت المزامنة العامة parseymdhms (مصدر السلسلة) {try {return sdfyhm.parse (source) ؛ } catch (parseException e) {E.PrintStackTrace () ؛ إرجاع تاريخ جديد () ؛ }}} أولاً ، دعنا نحلل:
تستخدم الوظيفة parseymdhms () في هذه المرحلة تعديل متزامن ، مما يعني أن العملية غير آمنة للمعلومات ، لذلك يجب مزامنتها. لا يمكن أن تكون مؤشر ترابط غير آمن إلا طريقة parse () لـ SimpleDateFormat. تحقق من رمز المصدر. هناك متغير عالمي في SimpleDateFormat.
تقويم تقويم محمي ؛ Date parse () {calendar.clear () ؛ ... / / أداء بعض العمليات لتعيين تاريخ التقويم وغيرها من التقويم. // احصل على وقت التقويم}عملية clear () سوف تسبب غير آمن الموضوع.
بالإضافة إلى ذلك ، فإن استخدام الكلمة الرئيسية المتزامنة له تأثير كبير على الأداء ، خاصةً عندما يكون متعدد الخيوط ، في كل مرة يطلق عليها طريقة parseymdhms ، سيتم إصدار حكم المزامنة ، والمزامنة نفسها مكلفة للغاية ، لذلك يعد هذا حلًا غير معقول.
طريقة التحسين
يحدث عدم آثار مؤشر الترابط بسبب استخدام المتغيرات المشتركة بواسطة مؤشرات ترابط متعددة ، لذلك نستخدم هنا ThreadLocal <SpimleDateFormat> لإنشاء متغير نسخة لكل مؤشر ترابط بشكل منفصل. قم أولاً بإعطاء الكود ، ثم قم بتحليل سبب هذه المشكلة لحل المشكلة.
/** * فئة أدوات التاريخ (يتم استخدام threadlocal للحصول على تبسيط ، ويمكن نسخ طرق أخرى مباشرة بواسطة مشترك lang) * author niu li * date 2016/11/19 */public class dateutil {private static map <string ، threadlocal <simpledateformat >> sdfmap = new ashmap ، logger static private = loggerFactory.getLogger (dateUtil.class) ؛ السلسلة الثابتة العامة MDHMSS = "MMDDHHMMSSSSSS" ؛ سلسلة ثابتة عامة ymdhms = "yyymmddhhmmss" ؛ سلسلة ثابتة عامة ymdhms_ = "yyyy-mm-dd hh: mm: ss" ؛ سلسلة ثابتة عامة ymd = "Yyyymmdd" ؛ سلسلة ثابتة عامة ymd_ = "yyyy-mm-dd" ؛ السلسلة الثابتة النهائية العامة HMS = "HHMMSS" ؛ / *** احصل على مثيل SDF من مؤشر الترابط المقابل استنادًا إلى المفتاح في مفتاح نمط الخريطة* param في الخريطة* @RETURN هذا المثيل*/ private static simpledateformat getDF (نمط السلسلة النهائية) {threadlocal <SimpleDateFormat> sdfthread = sdfmap.get (نمط) ؛ إذا كان (sdfthread == null) {// تحقق مزدوجًا لمنع SDFMAP من وضع القيمة في عدة مرات ، والسبب في القفل المزدوج المفرد هو نفسه متزامن (dateutil.class) {sdfthread = sdfmap.get (نمط) ؛ if (sdfthread == null) {logger.debug ("ضع sdf جديد للنمط" + pattern + "to map") ؛ sdfthread = new threadlocal <SimpleDateFormat> () {override محمية SimpleDateFormat initialValue () {logger.debug ("thread:" + thread.currentThread () + "init pattern:" + pattern) ؛ إرجاع New SimpleDateFormat (نمط) ؛ }} ؛ sdfmap.put (نمط ، sdfthread) ؛ }}} return sdfthread.get () ؛ } / *** تحليل التاريخ وفقًا للنمط المحدد* param التاريخ الذي يتم تحليله* param نمط التاريخ تحديد التنسيق* @RETURN مثيل* / تاريخ ثابت ثابت (تاريخ السلسلة ، نمط السلسلة) {if (date == null) {رمي غير قانونية جديدة ("لا يجب أن يكون التاريخ" } حاول {return getSdf (pattern) .parse (date) ؛ } catch (parseException e) {E.PrintStackTrace () ؛ logger.error ("التنسيق المحجوب لا يدعم:"+نمط) ؛ } إرجاع فارغ ؛ } / *** تاريخ التنسيق وفقًا للنمط المحدد* param تاريخ أن يتم تنسيقه* نمط param حدد التنسيق* @RETURN format* / public static string formatdate (تاريخ التاريخ ، نمط السلسلة) {if (date == null) } آخر {return getSdf (pattern) .Format (date) ؛ }}}امتحان
قم بتنفيذ واحدة في الخيط الرئيسي والآخران في خيط الطفل ، كلاهما باستخدام نفس النمط
public static void main (string [] args) {dateutil.formatdate (date () ، mdhmss) ؛ موضوع جديد (()-> {dateutil.formatdate (Date () new () ، mdhmss) ؛}). start () ؛ موضوع جديد (()-> {dateutil.formatdate (Date () new () ، mdhmss) ؛}). start () ؛ }تحليل السجل
ضع SDF جديد من نمط MMDDHHMMSSSSS إلى MapThread: Thread [Main ، 5 ، Main] init Pattern: MMDDHHMMSSSSTHREAD: Thread [thread-0،5 ، main] init pattern:
تحليل
يمكن ملاحظة أن SDFMAP تم إدخاله مرة واحدة ، في حين أن SimpleDateFormat كان جديدًا ثلاث مرات لأن هناك ثلاث مؤشرات ترابط في الكود. فلماذا هذا؟
لكل مؤشر ترابط ، هناك مرجع متغير عالمي إلى threadlocal.throadlocalmap threadlocals. هناك threadlocal.throadlocalmap الذي يحمل threadlocal والقيمة المقابلة. صورة واحدة أفضل من ألف كلمة. مخطط الهيكل كما يلي:
لذلك بالنسبة لـ SDFMAP ، سيتم تغيير مخطط الهيكل
1. تنفيذ أولا DateUtil.FormatDate (تاريخ جديد () ، MDHMSS) ؛
. . // sdfthread لا يزال فارغًا ، أدخل عبارة if if (sdfthread == null) {// print logger.debug ("ضع sdf جديد للنمط" + pattern + "إلى MAP") ؛ // إنشاء مثيل ThreadLocal وتجاوز طريقة initialValue sdfthread = new threadlocal <SimpleDateFormat> () {override محمية simpledateformat initialValue () {logger.debug ("مؤشر الترابط:" + thread.currentTrathread () + "نمط init:" + نمط) ؛ إرجاع New SimpleDateFormat (نمط) ؛ }} ؛ // ضبط في sdfmap sdfmap.put (نمط ، sdfthread) ؛ }}} return sdfthread.get () ؛ } في هذا الوقت ، قد يسأل شخص ما ، لا يتم استدعاء طريقة مجموعة ThreadLocal هنا ، فكيف تقوم بتعيين القيمة للدخول؟
هذا يتطلب تنفيذ sdfthread.get ():
public t get () {thread t = thread.currentThRead () ؛ threadlocalmap خريطة = getMap (t) ؛ if (map! = null) {threadlocalmap.entry e = map.getentry (this) ؛ if (e! = null) {suppressWarnings ("unchected") t result = (t) e.value ؛ نتيجة العودة }} return setInitialValue () ؛ }وهذا يعني ، عندما لا تكون القيمة غير موجودة ، سيتم استدعاء طريقة setInitialValue () ، والتي ستسمي طريقة initialValue () ، وهي الطريقة التي نتجاوزها.
طباعة السجل المقابلة.
ضع SDF جديد من نمط MMDDHHMMSSSS على MapThread: Thread [Main ، 5 ، Main] init Pattern: MMDDHHMMSSSSS
2. تنفيذ DateUtil.Formatdate (تاريخ جديد () ، MDHMSS) على موضوع الطفل في المرة الثانية ؛
// تنفيذ `dateutil.formatdate (تاريخ جديد () ، mdhmss) ؛` simpledateformat private getSdf (نمط السلسلة النهائية) {threadlocal <SimpleDateFormat> sdfthread = sdfmap.get (نمط) ؛ . if (sdfthread == null) {logger.debug ("ضع sdf جديد للنمط" + pattern + "to map") ؛ sdfthread = new threadlocal <SimpleDateFormat> () {override محمية SimpleDateFormat initialValue () {logger.debug ("thread:" + thread.currentThread () + "init pattern:" + pattern) ؛ إرجاع New SimpleDateFormat (نمط) ؛ }} ؛ sdfmap.put (نمط ، sdfthread) ؛ }}} // call sdfthread.get () مباشرة لإرجاع sdfthread.get () ؛ }تحليل sdfthread.get ()
// تنفيذ `dateutil.formatdate (تاريخ جديد () ، mdhmss) ؛` public t get () {thread t = thread.currentTherAd () ؛ // احصل على خريطة Threadlocalmap الحالية للأطفال = getMap (t) ؛ . if (e! = null) {suppressWarnings ("unchected") t result = (t) e.value ؛ نتيجة العودة }} // تنفيذ التهيئة مباشرة ، أي استدعاء طريقة initialValue () نقوم بإرجاع setInitialValue () ؛ }السجل المقابل:
Thread [thread-1،5 ، main] init نمط: mmddhhmmssssss
لخص
في أي سيناريو أكثر ملاءمة لاستخدام Threadlocal؟ أعطى شخص ما إجابة جيدة على Stackoverflow.
متى وكيف يمكنني استخدام متغير threadlocal؟
أحد الاستخدامات الممكنة (والشائعة) هو عندما يكون لديك بعض الكائنات التي لا تكون آمنة مؤشرات الترابط ، لكنك تريد تجنب مزامنة الوصول إلى هذا الكائن (أنا أنظر إليك ، SimpleDateFormat). بدلاً من ذلك ، أعط كل موضوع مثيله الخاص للكائن.
الرمز المرجعي:
https://github.com/nl101531/util-demo تحت Javaweb
مراجع:
تعلم java threadlocal بطريقة سهلة الفهم
مشكلة سلامة مؤشر ترابط SimpleTformats وحلها
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.