الكلمات الرئيسية المتزامنة
عند استخدام الكلمة الرئيسية لغة Java لتعديل طريقة أو كتلة رمز ، يمكن أن تضمن أن مؤشر ترابط واحد ينفذ رمز واحد في نفس الوقت.
عندما يصل اثنين من مؤشرات الترابط المتزامنة إلى كتلة الكود المتزامنة (هذا) في نفس كائن الكائن ، يمكن تنفيذ مؤشر ترابط واحد فقط خلال فترة واحدة. يجب أن ينتظر مؤشر ترابط آخر حتى يقوم مؤشر الترابط الحالي بتنفيذ كتلة الرمز هذه قبل تنفيذ كتلة الكود.
ومع ذلك ، عندما يصل مؤشر ترابط واحد إلى كتلة رمز التزامن (هذا) متزامن للكائن ، لا يزال بإمكان مؤشر ترابط آخر الوصول إلى كتلة رمز التزامن غير المتزامنة (هذا) في هذا الكائن.
من الأهمية بمكان أنه عندما يصل مؤشر الترابط إلى كتلة رمز المزامنة (هذا) متزامن للكائن ، ستقوم مؤشرات الترابط الأخرى بحظر الوصول إلى جميع كتل المزامنة (هذا) متزامنة في الكائن.
ينطبق المثال الثالث أيضًا على كتل التعليمات البرمجية المتزامنة الأخرى. أي عندما يصل مؤشر ترابط إلى كتلة رمز المزامنة (هذا) متزامن لكائن ، فإنه يحصل على قفل كائن هذا الكائن. نتيجة لذلك ، يتم حظر مؤشرات الترابط الأخرى إلى جميع أجزاء الكود المتزامن لكائن الكائن مؤقتًا.
تنطبق القواعد المذكورة أعلاه أيضًا على أقفال الكائنات الأخرى.
مثال رمز
Package Test160118 ؛ الفئة العامة testsynchronized {public static void main (string [] args) {sy sy = new sy (0) ؛ Sy Sy2 = New Sy (1) ؛ Sy.start () ؛ Sy2.start () ؛ }} class sy يمتد thread {private int flag ؛ كائن ثابت x1 = كائن جديد () ؛ كائن ثابت x2 = كائن جديد () ؛ sy sy (int flag) {this.flag = flag ؛ } Override public void run () {system.out.println (flag) ؛ حاول {if (flag == 0) {synchronized (x1) {system.out.println (flag+"locked x1") ؛ thread.sleep (1000) ؛ Synchronized (x2) {system.out.println (flag+"locked x2") ؛ } system.out.println (flag+"الإصدار x1 و x2") ؛ }} if (flag == 1) {synchronized (x2) {system.out.println (flag+"locked x2") ؛ thread.sleep (1000) ؛ Synchronized (x1) {system.out.println (flag+"locked x1") ؛ } system.out.println (flag+"الإصدار x1 و x2") ؛ }}} catch (interruptedException e) {E.PrintStackTrace () ؛ }}}
موضوع خالي من قفل Threadlocal مبدأ التنفيذ
ماذا يمكن أن يفعل ThreadLocal؟
من الصعب القول هذه الجملة. دعنا نلقي نظرة على بعض الصعوبات التي واجهتها في المشروع الفعلي: عندما تتصل ببعض الأساليب وفقًا لبعض المعلمات في المشروع ، فإن الطريقة تستدعي الطريقة ، ثم استدعاء الطريقة عبر الكائنات ، قد تستخدم هذه الأساليب بعض المعلمات المماثلة ، على سبيل المثال ، تتطلب A المعلمات A و B و C في A. بعد A مكالمات B و B و B و C في B و B ، و B ، في هذا الوقت ، يجب نقل جميع المعلمات إلى B. وهلم جرا. إذا كان هناك العديد من الطرق التي تسمى ، ستصبح المعلمات أكثر وأكثر. بالإضافة إلى ذلك ، عندما يحتاج البرنامج إلى إضافة معلمات ، تحتاج إلى إضافة معلمات إلى الأساليب ذات الصلة واحدة تلو الأخرى. نعم ، إنه أمر مزعج للغاية. أعتقد أنك واجهته. هذا أيضًا بعض طرق المعالجة الشائعة للغة الموجهة إلى الكائنات بلغة C. ومع ذلك ، فإن طريقة المعالجة البسيطة لدينا هي لفه في كائن وتمريره. يمكن حل هذه المشكلة عن طريق إضافة سمات الكائن. ومع ذلك ، عادة ما تكون الكائنات ذات معنى ، لذلك في بعض الأحيان تضيف عبوة الكائنات البسيطة بعض السمات غير ذات الصلة الممتدة لجعل تعريف الفئة لدينا غريبًا للغاية ، لذلك في هذه الحالات عندما ننظم برامج معقدة مثل هذا ، نستخدم بعض النطاقات المشابهة للنطاق للتعامل معها. الأسماء والاستخدامات أكثر شيوعا. على غرار تطبيقات الويب ، سيكون هناك نطاقات في السياق والجلسة والطلب ومستويات الصفحة. يمكن لـ ThreadLocal أيضًا حل هذا النوع من المشكلات ، ولكنه غير مناسب جدًا لحل هذا النوع من المشكلات. عند مواجهة هذه المشكلات ، عادةً ما لا يمررها في المراحل المبكرة من النطاق والكائن ، ويعتقد أنه لن تتم إضافة المعلمات. ، عند إضافة معلمات ، وجدت أن هناك العديد من الأماكن التي يتعين تغييرها. من أجل عدم تدمير بنية الكود ، من الممكن أن يكون هناك الكثير من المعلمات ، مما قلل من قابلية قراءة رمز الطريقة. تتم إضافة threadlocal للتعامل معها. على سبيل المثال ، عندما تستدعي طريقة ما طريقة أخرى ، يتم تمرير 8 معلمات ، ويتم تمرير إحدى المعلمات عن طريق استدعاء طبقة الأسلوب التاسع حسب الطبقة. في هذا الوقت ، تحتاج الطريقة الأخيرة إلى إضافة معلمة واحدة. من الطبيعي أن تصبح الطريقة الأولى 9 معلمات ، ولكن في هذا الوقت ، سيتم تورط الأساليب ذات الصلة ، مما يجعل الكود منتفخًا.
The Threadlocal المذكور أعلاه هو غرض من إصلاح مشكلة فقدان الأغنام ، لكنها ليست وسيلة موصى بها بشكل خاص لاستخدامها. كما أن لديها بعض الطرق المماثلة لاستخدامها ، أي أن هناك العديد من المكالمات الديناميكية على مستوى الإطار ، ويجب الوفاء ببعض البروتوكولات أثناء عملية المكالمات. على الرغم من أننا نحاول أن نكون عالميين ، إلا أن العديد من المعلمات الموسعة ليس من السهل مراعاتها عند تحديد البروتوكول ، ويتم ترقية الإصدار أيضًا في أي وقت. ومع ذلك ، عندما يتم تمديد الإطار ، تكون الواجهة أيضًا توافقًا عالميًا ومتخلفًا. نحتاج إلى بعض المحتويات الممتدة لتكون مريحة وبسيطة.
ببساطة ، يحول ThreadLocal بعض ملحقات النظام المعقدة إلى تعريفات بسيطة ، مما يجعل الأجزاء المشاركة في المعلمات ذات الصلة سهلة للغاية. هنا مثالنا:
في مدير معاملات الربيع ، يتم وضع الاتصال الذي تم الحصول عليه بواسطة مصدر البيانات في ThreadLocal. بعد تنفيذ البرنامج ، يتم الحصول على الاتصال من ThreadLocal ثم يتم الالتزام والتراجع. قيد الاستخدام ، من الضروري التأكد من الحصول على الاتصال الذي تم الحصول عليه بواسطة البرنامج من خلال مصدر البيانات من الربيع. لماذا مثل هذه العملية؟ نظرًا لأن رمز العمل يتم تحديده بالكامل من خلال التطبيق ، ولا يمكن للإطار أن يطلب من رمز العمل من كتابة ، وإلا فإن الإطار سيفقد فائدة عدم السماح لرمز العمل بإدارة الاتصال. بعد تخفيض رمز العمل ، لن يمر الربيع اتصالًا بمنطقة رمز العمل. يجب أن يتم حفظها في مكان. عندما تمر الطبقة الأساسية عبر ibatis والربيع. عندما يحصل إطار عمل مثل JDBC على اتصال مصدر البيانات نفسه ، فإنه سيتطلب الحصول عليه وفقًا للقواعد المتفق عليها في الربيع. نظرًا لأن عملية التنفيذ تتم معالجتها في نفس مؤشر الترابط ، يتم الحصول على نفس الاتصال لضمان أن الاتصال المستخدم هو نفسه أثناء عمليات الالتزام والتراجع والتجارية ، لأن نفس Connecton فقط يمكن أن يضمن المعاملات ، وإلا فإن قاعدة البيانات نفسها غير مدعومة.
في الواقع ، في العديد من تطبيقات البرمجة المتزامنة ، يلعب ThreadLocal دورًا مهمًا للغاية. لا يضيف أقفال ويغلق بسهولة الخيوط بسلاسة ، دون الحاجة إلى إعادة تخصيص مساحة في كل مرة مثل المتغيرات المحلية. نظرًا لأن العديد من المساحات آمنة من مؤشرات الترابط ، فيمكنها الاستفادة مرارًا وتكرارًا للمخازن المؤقتة الخاصة بالخيوط.
كيفية استخدام threadlocal؟
تحديد متغير Threadlocal في أي موقع مناسب في النظام ، والذي يمكن تعريفه كنوع ثابت عام (كائن Threadlocal جديد مباشرة). إذا كنت ترغب في وضع البيانات فيها ، فاستخدم Set (كائن) ، واستخدم تشغيل () التشغيل ، واستخدم إزالة () عند حذف العناصر. الأساليب الأخرى هي الأساليب غير العامة ، ولا ينصح بها.
فيما يلي مثال بسيط (قصاصة الكود 1):
الفئة العامة threadlocaltest2 {public final static threadlocal <string> test_thread_name_local = new threadlocal <string> () ؛ Public Final Static Threadlocal <string> test_thread_value_local = new threadlocal <string> () ؛ public static void main (string [] args) {for (int i = 0 ؛ i <100 ؛ i ++) {final string name = "thread- [" + i + "]" ؛ قيمة السلسلة النهائية = string.valueof (i) ؛ New Thread () {public void run () {try {test_thread_name_local.set (name) ؛ test_thread_value_local.set (value) ؛ calla () ؛ } أخيرًا {test_thread_name_local.remove () ؛ test_thread_value_local.remove () ؛ } } } }.يبدأ()؛ }} public static void calla () {callb () ؛ } public static void callb () {new threadlocaltest2 (). callc () ؛ } public void callc () {calld () ؛ } public void calld () {system.out.println (test_thread_name_local.get () + "/t =/t" + test_thread_value_local.get ()) ؛ }}نحن هنا محاكاة 100 مؤشر ترابط للوصول إلى الاسم والقيمة على التوالي. يتم تعيين قيم الاسم والقيمة عمدا على نفسه في الوسط لمعرفة ما إذا كانت هناك مشكلة تزامن. من خلال الإخراج ، يمكننا أن نرى أن إخراج مؤشر الترابط لا يتم إخراجه بالترتيب ، مما يعني أنه يتم تنفيذه بالتوازي ، ويمكن أن يكون اسم الخيط والقيمة مقابلة. في الوسط ، من خلال طرق متعددة ، لا يتم تمرير المعلمات في المكالمة الفعلية ، لذا كيفية الحصول على المتغيرات المقابلة. ومع ذلك ، في الأنظمة الفعلية ، غالبا ما يعبرون الفصول. هنا يتم محاكاة فقط في فصل واحد. في الواقع ، فصول الصليب هي نفس النتيجة. يمكنك محاكاةها بنفسك.
أعتقد أنه بعد رؤية هذا ، يهتم العديد من المبرمجين جدًا بمبدأ ThreadLocal. دعونا نرى كيف يتم ذلك. على الرغم من عدم تمرير المعلمات ، يمكنها استخدامها مثل المتغيرات المحلية. إنه بالفعل سحري تمامًا. في الواقع ، يمكنك معرفة أنها طريقة إعداد. عندما ترى أن الاسم يجب أن يكون مرتبطًا بالموضوع ، فقل أقل هراء ، دعنا نلقي نظرة على رمز المصدر الخاص به. نظرًا لأننا نستخدم أكثر مجموعة ، واحصل وإزالة ، ثم ابدأ بالمجموعة:
طريقة SET (T OBJ) هي (Code Snippet 2):
مجموعة void العامة (t value) {thread t = thread.currentThRead () ؛ threadlocalmap خريطة = getMap (t) ؛ if (map! = null) map.set (هذا ، القيمة) ؛ Else CreateMap (t ، القيمة) ؛}أولاً ، يتم الحصول على الخيط الحالي ، مثل التخمين ، ثم هناك طريقة getMap ، التي تمر في الخيط الحالي. يمكننا أولاً أن نفهم أن هذه الخريطة عبارة عن خريطة تتعلق بالخيط. بعد ذلك ، إذا لم يكن فارغًا ، فقم بتشغيل المجموعة. عند متابعته ، ستجد أن هذا يشبه عملية HashMap ، أي يتم كتابة جزء من البيانات في الخريطة. إذا كان فارغًا ، يتم استدعاء طريقة CreateMap. بعد الدخول ، ألقِ نظرة (Code Snippet 3):
void createMap (Thread t ، t firstvalue) {t.Threadlocals = new threadlocalmap (هذا ، أول ،}}يقوم استرداد النقود بإنشاء threadlocalmap ، ويكتب المعلمات التي تم تمريرها و threadlocal الحالية كهيكل kV (قصاصة الكود 4):
threadlocalmap (threadlocal firstkey ، Object firstValue) {table = new entry [initial_capacity] ؛ int i = firstKey.ThreadLocalhashCode & (initial_capacity - 1) ؛ الجدول [i] = إدخال جديد (FirstKey ، FirstValue) ؛ الحجم = 1 ؛ setThreshold (initial_capacity) ؛}لم يتم شرح هذا هنا التفاصيل الهيكلية لـ threadlocalmap. ما عليك سوى معرفة أن تنفيذها يشبه HashMap. هناك العديد من الطرق التي لا تحتوي على خريطة تطبيقات ، لأنها لا تريد منك الحصول على خريطة من خلال بعض الطرق (مثل الانعكاس) لزيادة تشغيلها. إنها فئة داخلية ثابتة في نوع Threadlocal ، والفئات الافتراضية ، ويمكن فقط أن تشير الفئات الموجودة تحت Java.lang ، حتى تتمكن من التفكير في مؤشر الترابط.
دعونا نلقي نظرة على طريقة getMap ، لأنني أعلم فقط أن الخريطة التي تم الحصول عليها مرتبطة بالمعلومات ، ومن خلال قصاصة الكود 3 ، يوجد T.Threadlocalmap = جديد ThreadLocalmap (هذا ، القيمة الأولى) ، أعتقد أنه يجب عليك أن تفهم على الأرجح أن هذا المتغير يجب أن يأتي من موضوع. دعنا ندخل وفقًا لطريقة getMap:
threadlocalmap getMap (موضوع t) {return t.Threadlocals ؛}نعم ، إنه يأتي من موضوع ، ويحدث هذا الموضوع هو الخيط الحالي ، لذا انتقل إلى التعريف:
threadlocal.throadlocalmap threadlocals = null ؛
هذه الخاصية موجودة في فئة مؤشرات الترابط ، أي أن كل مؤشر ترابط يحتوي على مؤشر الترابط بشكل افتراضي ، والذي يتم استخدامه لتخزين المتغيرات المحلية على مستوى مؤشر الترابط. عادة لا يمكنك تعيين قيم لها ، لأن هذه المهام عادة ما تكون غير آمنة.
يبدو فوضويًا بعض الشيء ، لا تقلق ، دعنا ننظر إلى الوراء ونستكشف الأفكار:
1. هناك خاصية في موضوع يشبه HashMap ، ولكن اسمها هو threadlocalmap. هذه الخاصية من النوع الافتراضي ، لذلك يمكن الرجوع إلى جميع الفئات الموجودة أسفل الحزمة نفسها. نظرًا لأنه متغير محلي للخيط ، فإن كل مؤشر ترابط له خريطة منفصلة خاصة به ، والتي لا تتعارض مع بعضها البعض ، لذلك حتى لو تم تعريف Threadlocal على أنها مؤشرات ترابط ثابت ، فلن يكون هناك تعارض.
2. Threadlocal وخيط تحت نفس الحزمة. يمكنك الرجوع إلى هذا الفصل والعمل عليه. في هذا الوقت ، يحدد كل مؤشر ترابط واحد ، استخدم هذا كمفتاح ، والقيمة التي تمر بها كقيمة ، وهذه هي threadlocal التي تحددها. لذلك ، تستخدم متغيرات Threadlocal المختلفة مجموعة ، ولن تتعارض البيانات بين بعضها البعض ، لأن مفاتيحها مختلفة. بطبيعة الحال ، بعد نفس threadlocal العمليتين المحددتين ، ستكون المرة الأخيرة السائدة.
3. لتلخيص ، عندما تكون المواضيع متوازية ، يمكن استخدام ThreadLocal مثل المتغيرات المحلية ، وهي آمنة مؤشرات الترابط ، والبيانات بين متغيرات Threadlocal المختلفة ليس لها تعارض.
دعنا نستمر في النظر إلى طريقة GET وإزالة الطريقة ، إنها في الواقع بسيطة:
public t get () {thread t = thread.currentThRead () ؛ threadlocalmap خريطة = getMap (t) ؛ if (map! = null) {threadlocalmap.entry e = map.getentry (this) ؛ if (e! = null) return (t) e.value ؛ } return setInitialValue () ؛}من خلال استدعاء طريقة getMap وفقًا للخيط الحالي ، أي ، يتم استدعاء T.Threadlocalmap ، ثم البحث في الخريطة ، لاحظ أنه تم العثور على الخريطة مع الإدخال ، أي الهيكل الأساسي لـ KV ، لأنك تكتب فقط إلى القيمة ، فستقوم بتعيين قيمة E.Value لإرجاع القيمة التي كتبتها ، لأن المفتاح هو مؤشر ترابط نفسه. يمكنك أن ترى أن map.getentry يتم الحصول عليها أيضًا من خلال هذا.
نفس طريقة إزالة هي:
void public remove () {threadlocalmap m = getMap (thread.currentThRead ()) ؛ إذا (m! = null) m.remove (this) ؛}أيضا ، يتم الحصول على الخريطة بناء على مؤشر الترابط الحالي. إذا لم يكن فارغًا ، فقم بإزالته وإزالته من خلال هذا.
بالإضافة إلى ذلك (2013-6-29) ، ما هي عيوب نسيان الكتابة؟ ما هي المزالق الموجودة في هذا الخيط؟ يجب أن ترى من المثال السابق أن الكائن المتعلق بالمعلومات المرتبط بخريطة ، وأن هذه الخريطة هي خاصية لخيط مؤشر الترابط. ثم هناك مشكلة في أنه إذا لم تقم بإزالة نفسك أو إذا كنت لا تعرف متى يتم الإزالة في برنامجك الخاص ، فلن يتم تسجيل سلسلة مؤشر الترابط ، ولن يتم تسجيل مجموعة البيانات في.
من ناحية أخرى ، ما لم تفهم بوضوح مكان تعيين هذا الكائن وأين يتم إزالته. إذا كانت غامضة ، فمن المحتمل جدًا ألا يذهب الكود الخاص بك إلى وضع إزالة ، أو يسبب بعض المشكلات المنطقية. بالإضافة إلى ذلك ، إذا لم تتم إزالته ، فيجب عليك انتظار تسجيل الموضوع. في العديد من خوادم التطبيق ، يتم إعادة استخدام المواضيع لأنه لا يزال هناك تكاليف في مؤشرات ترابط تخصيص النواة ، لذلك من الصعب تسجيل مؤشرات الترابط في هذه التطبيقات. ثم لا يمكن تسجيل البيانات المكتوبة إلى ThreadLocal بشكل طبيعي. قد تكون مخفية عن طريق الخطأ واستخدامها عندما نستخدم بعض الأطر المفتوحة المصدر ، والتي قد تسبب مشاكل. أخيرًا ، وجدت أنه عندما OOM ، تأتي البيانات بالفعل من ThreadLocalmap. لا أعرف أين يتم تعيين هذه البيانات ، لذلك يجب عليك الانتباه إلى هذه الحفرة ، وسقط أكثر من شخص في هذه الحفرة.