1. قفل الوزن الثقيل
في المقالة السابقة ، قدمنا استخدام مبادئ التنفيذ المتزامنة. الآن يجب أن نعلم أن المزامنة يتم تنفيذها من خلال قفل شاشة داخل الكائن. ومع ذلك ، يتم تنفيذ قفل الشاشة بشكل أساسي من خلال الاعتماد على قفل Mutex لنظام التشغيل الأساسي. يحتاج نظام التشغيل إلى التبديل بين مؤشرات الترابط من حالة المستخدم إلى الحالة الأساسية. هذا مكلف للغاية ، والتحويل بين الحالات يستغرق وقتًا طويلاً نسبيًا. هذا هو السبب في أن المزامنة غير فعالة. لذلك ، نسمي هذا القفل الذي يعتمد على تنفيذ قفل Mutex لنظام التشغيل "قفل الوزن الثقيل". إن جوهر التحسينات المختلفة التي تم إجراؤها على المزامنة في JDK هو تقليل استخدام هذا القفل الثقيل. بعد JDK1.6 ، من أجل تقليل استهلاك الأداء الناجم عن الحصول على الأقفال وإصدارها وتحسين الأداء ، تم تقديم "أقفال خفيفة الوزن" و "أقفال متحيزة".
2. قفل خفيف الوزن
هناك أربعة أنواع من حالات القفل: حالة خالية من القفل ، قفل متحيز ، قفل خفيف الوزن وقفل الوزن الثقيل. من خلال منافسة الأقفال ، يمكن ترقية الأقفال من الأقفال المتحيزة إلى أقفال خفيفة الوزن ، ثم ترقية الأقفال الثقيلة (ولكن ترقية الأقفال في اتجاه واحد ، مما يعني أنه لا يمكنها إلا الترقية من منخفضة إلى أعلى ، ولن يكون هناك تدهور للأقفال). في JDK 1.6 ، يتم تمكين قفل التحيز وقفل خفيف الوزن افتراضيًا. يمكننا أيضًا تعطيل القفل التحيز بواسطة -xx: -usebiasedlocking. يتم حفظ حالة القفل في ملف رأس الكائن ، مع أخذ 32 بت JDK كمثال:
حالة القفل | 25 بت | 4bit | 1bit | 2b | ||
23 بت | 2b | هل هو قفل متحيز؟ | قفل العلم بت | |||
قفل خفيف الوزن | مؤشر لقفل السجلات في المكدس | 00 | ||||
قفل الوزن الثقيل | مؤشر إلى Mutex (قفل الوزن الثقيل) | 10 | ||||
علامات GC | باطل | 11 | ||||
قفل إيجابي | معرف الموضوع | عصر | الموضوعات من العمر | 1 | 01 | |
قفل | Hashcode من الكائن | الموضوعات من العمر | 0 | 01 | ||
"الوزن الخفيف" نسبة إلى الأقفال التقليدية التي تستخدم Mutexes لنظام التشغيل. ومع ذلك ، من المهم التأكيد أولاً على أن الأقفال الخفيفة لا تستخدم لاستبدال أقفال الوزن الثقيل. نيتها الأصلية هي تقليل استهلاك الأداء الناتج عن استخدام أقفال الوزن الثقيلة التقليدية دون منافسة متعددة الخيوط. قبل شرح عملية تنفيذ الأقفال الخفيفة ، نفهم أولاً أن السيناريوهات التي تم تكييفها مع أقفال خفيفة الوزن هي الحالة التي تنفذ فيها مؤشرات الترابط كتل متزامنة بالتناوب. إذا تم الوصول إلى نفس القفل في نفس الوقت ، فسيتوسع قفل الوزن الخفيف إلى قفل الوزن الثقيل.
1. عملية قفل قفل الوزن الخفيف
(1) عندما يدخل الرمز إلى كتلة التزامن ، إذا كانت حالة قفل كائن التزامن خالية من القفل (علامة القفل هي "01" ، سواء كان ذلك قفلًا متحيزًا هو "0") ، فسيقوم الجهاز الافتراضي أولاً بإنشاء مساحة تسمى سجل القفل في إطار المكدس للمعلومات الحالية لتخزين النسخة الحالية من كلمة علامة القفل الحالية ، والتي تُدعى رسميًا كلمة مارك. في هذا الوقت ، يتم عرض حالة مكدس الخيط ورأس الكائن في الشكل 2.1.
(2) انسخ كلمة علامة في رأس الكائن ونسخها إلى سجل القفل.
(3) بعد نجاح النسخة ، سيستخدم الجهاز الظاهري عمليات CAS لمحاولة تحديث كلمة علامة الكائن إلى مؤشر لقفل السجل ، وتوجيه مؤشر المالك في سجل القفل إلى كلمة الكائن. إذا كان التحديث ناجحًا ، فعليك تنفيذ الخطوة (3) ، وإلا قم بتنفيذ الخطوة (4).
(4) إذا نجح هذا الإجراء في التحديث ، فإن مؤشر الترابط يحتوي على قفل الكائن ، ويتم تعيين علامة قفل علامة علامة الكائن على "00" ، مما يعني أن الكائن في حالة قفل خفيفة الوزن. في هذا الوقت ، يتم عرض حالة مكدس الخيط ورأس الكائن في الشكل 2.2.
(5) في حالة فشل عملية التحديث هذه ، سيقوم الجهاز الظاهري أولاً بالتحقق مما إذا كانت كلمة علامة الكائن تشير إلى إطار المكدس للمعلومات الحالية. إذا كان الأمر كذلك ، فهذا يعني أن الخيط الحالي يحتوي بالفعل على قفل الكائن ، ثم يمكنه إدخال كتلة التزامن مباشرة لمتابعة التنفيذ. بخلاف ذلك ، تتنافس خيوط متعددة على الأقفال ، وسيتوسع قفل الوزن الخفيف إلى قفل الوزن الثقيل ، وستصبح قيمة حالة العلم "10". يتم تخزين المؤشر إلى قفل الوزن الثقيل (Mutex) في Mark Word ، وسيدخل الخيط الذي ينتظر القفل أيضًا حالة الحظر. يحاول الخيط الحالي استخدام الدوران لاكتساب القفل. الدوران هو تجنب حظر الخيط واستخدام حلقة لاكتساب القفل.
الشكل 2.1 حالة المكدس والكائن قبل عملية قفل الخفيفة الوزن CAS
الشكل 2.2 حالة المكدس والكائن بعد عملية قفل خفيفة الوزن CAS
2. فتح عملية قفل خفيف الوزن:
(1) حاول استبدال كائن كلمة Mark النازح النسخ في مؤشر الترابط من خلال عملية CAS.
(2) إذا نجح الاستبدال ، فسيتم إكمال عملية التزامن بأكملها.
(3) إذا فشل البديل ، فهذا يعني أن الخيوط الأخرى قد حاولت الحصول على القفل (تم توسيع القفل في هذا الوقت) ، فيجب إيقاظ الخيط المعلق أثناء إطلاق القفل.
3. قفل إيجابي
تتمثل إدخال قفل التحيز في تقليل مسارات تنفيذ قفل القفل الخفيفة غير الضرورية دون منافسة متعددة الخيوط ، لأن الاستحواذ وإصدار أقفال خفيفة الوزن يعتمد على تعليمات ذرية متعددة CAS ، في حين يجب أن يتم إلغاء قفلات التحيز فقط على الاعتماد على الإرشادات الواحدة في الإلغاء. استهلاك التعليمات الذرية CAS). كما ذكر أعلاه ، يتم استخدام الأقفال الخفيفة الوزن لتحسين الأداء عندما تنفذ مؤشرات الترابط بالتناوب كتل متزامنة ، في حين يتم استخدام الأقفال المتحيزة لتحسين الأداء عندما يقوم مؤشر ترابط واحد فقط بتنفيذ كتل متزامنة.
1. عملية الاستحواذ على القفل المنحيزة:
(1) الوصول إلى ما إذا كان علم قفل التحيز في علامة Mark يتم تعيينه على 1 ، وما إذا كانت علامة القفل هي 01 - تؤكد أنها حالة قابلة للتحيز.
(2) إذا كانت حالة قابلة للتحيز ، فاختبر ما إذا كان معرف الخيط يشير إلى مؤشر الترابط الحالي. إذا كان الأمر كذلك ، أدخل الخطوة (5) ، وإلا أدخل الخطوة (3).
(3) إذا لم يشير معرف الخيط إلى مؤشر الترابط الحالي ، فسيتم تنافس القفل من خلال عملية CAS. إذا نجحت المنافسة ، فقم بتعيين معرف مؤشر الترابط في Mark Word على معرف مؤشر الترابط الحالي وتنفيذ (5) ؛ إذا فشلت المنافسة ، فإن تنفيذ (4).
(4) إذا فشلت CAS في الحصول على قفل التحيز ، فهذا يعني أن هناك منافسة. عندما يتم الوصول إلى SafePoint العالمي ، يتم تعليق الخيط الذي يحصل على قفل التحيز. تتم ترقية قفل التحيز إلى قفل خفيف الوزن ، ويستمر الخيط المحظور في SafePoint في تنفيذ رمز التزامن.
(5) تنفيذ رمز التزامن.
2. إطلاق القفل المتحيز:
تم ذكر إلغاء القفل المتحيز في الخطوة الرابعة أعلاه. سيصدر القفل المتحيز القفل فقط عندما تحاول مؤشرات الترابط الأخرى التنافس على القفل المتحيز ، ولن يطلق الخيط بنشاط القفل المتحيز. يتطلب إلغاء القفل المتحيز في انتظار نقطة الأمان العالمية (لا يتم تنفيذ أي رمز ثانوي في هذه المرحلة الزمنية). سيؤدي أولاً إلى إيقاف مؤشر الترابط مع القفل المتحيز ، ويحدد ما إذا كان كائن القفل في حالة مغلقة ، ثم العودة إلى الجزء غير المؤمن (العلم هو "01") أو قفل خفيف الوزن (بت العلم هو "00") بعد التراجع عن القفل المتحيز.
3. التحويل بين قفل الوزن الثقيل وقفل الوزن وخفيفة الوزن وقفل التحيز
الشكل 2.3 مخطط التحويل من الثلاثة
هذه الصورة هي في الأساس ملخص للمحتوى أعلاه. إذا كان لديك فهم جيد للمحتوى أعلاه ، فيجب أن تكون الصورة سهلة الفهم.
4. تحسينات أخرى
1. الغزل التكيفي: من عملية الحصول على أقفال خفيفة الوزن ، نعلم أنه عندما يفشل مؤشر ترابط في إجراء عملية CAS أثناء الاستحواذ على أقفال خفيفة الوزن ، من الضروري الحصول على قفل الوزن الثقيل من خلال الدوران. المشكلة هي أن الدوران يتطلب استهلاك وحدة المعالجة المركزية. إذا تعذر الحصول على القفل ، فسيكون الخيط في حالة تدور وموارد وحدة المعالجة المركزية بلا جدوى. أسهل طريقة لحل هذه المشكلة هي تحديد عدد الدورات ، على سبيل المثال ، اتركها دورة 10 مرات ، وإدخال حالة حظر إذا لم يتم الحصول على القفل. لكن JDK يتبنى مقاربة أكثر ذكاء - تدور التكيفي. ببساطة ، إذا نجح الخيط ، فإن عدد الدورات سيكون أكثر في المرة القادمة ، وإذا فشل الدوران ، فسيتم تقليل عدد الدورات.
2. قفل الغزو: يجب أن يكون مفهوم القفل الخشن أسهل في الفهم ، وهو دمج عمليات القفل وإلغاء القفل المتصل معًا عدة مرات في وقت واحد ، مما يوسع أقفال مستمرة متعددة في قفل مع نطاق أكبر. على سبيل المثال:
package com.paddx.test.string ؛ public class stringBufferTest {StringBuffer StringBuffer = new StringBuffer () ؛ public void append () {StringBuffer.Append ("a") ؛ StringBuffer.Append ("B") ؛ StringBuffer.Append ("C") ؛ }}هنا ، في كل مرة تتصل فيها بطريقة stringbuffer.append ، مطلوب قفل وإلغاء التغذية. إذا اكتشف الجهاز الظاهري سلسلة من عمليات القفل وفتح على نفس الكائن ، فسيقوم بدمجها في نطاق أكبر من عمليات القفل وإلغاء القفل ، أي أن القفل يتم تنفيذه على طريقة إلحاق أول ، ويتم تنفيذ الإلغاء بعد اكتمال طريقة إلحاق الأخيرة.
3. القفل القفل: يعني التخلص من القفل إزالة عمليات القفل غير الضرورية. وفقًا لتكنولوجيا Code Escape ، إذا تم تحديد أن جزءًا من التعليمات البرمجية والبيانات الموجودة على الكومة لن يهربوا من الخيط الحالي ، فيمكن اعتبار أن قطعة الكود هذه آمنة للمؤسسة وليس هناك حاجة إلى قفلها. انظر إلى البرنامج التالي:
package com.paddx.test.concurrent ؛ الفئة العامة synchronizedTest02 {public static void main (string [] args) {synchronizedTest02 test02 = new SynchronizedTest02 () ؛ // ابدأ الاحماء لـ (int i = 0 ؛ i <10000 ؛ i ++) {i ++ ؛ } start = system.currentTimeMillis () ؛ لـ (int i = 0 ؛ i <100000000 ؛ i ++) {test02.append ("ABC" ، "def") ؛ } system.out.println ("time =" + (System.CurrentTimeMillis () - start)) ؛ } public void append (String Str1 ، String str2) {StringBuffer SB = new StringBuffer () ؛ SB.Append (Str1) .Append (Str2) ؛ }}على الرغم من أن إلحاق StringBuffer هو طريقة متزامنة ، إلا أن StringBuffer في هذا البرنامج ينتمي إلى متغير محلي ولن يهرب من الطريقة. لذلك ، هذه العملية هي في الواقع آمنة الخيط ويمكن أن تقضي على القفل. هنا نتيجة لإعداماتي المحلية:
لتقليل تأثير العوامل الأخرى ، يتم تعطيل قفل التحيز (-xx: -usebiasedLocking) هنا. من خلال البرنامج أعلاه ، يمكن ملاحظة أن الأداء قد تم تحسينه بشكل كبير بعد القضاء على القفل.
ملاحظة: قد تكون نتائج التنفيذ مختلفة بين الإصدارات المختلفة من JDK. إصدار JDK الذي أستخدمه هنا هو 1.6.
5. ملخص
تركز هذه المقالة على تحسين المزامنة في JDK ، مثل الأقفال الخفيفة والأقفال المتحيزة ، لكن هذين الأقفان ليسا بدون أوجه قصور. على سبيل المثال ، عندما تكون المنافسة شرسة ، فلن تفشل فقط في تحسين الكفاءة ، ولكنها ستقلل من الكفاءة ، لأن هناك عملية ترقية إضافية. في هذا الوقت ، من الضروري تعطيل الأقفال المتحيزة من خلال -xx: -usebiasedLocking. فيما يلي مقارنة بين هذه الأقفال:
قفل | ميزة | عيب | السيناريوهات المعمول بها |
قفل إيجابي | لا يتطلب القفل وفتح استهلاك إضافي ، وهناك فجوة نانو ثانية مقارنة بأداء طريقة غير متزامنة. | إذا كانت هناك منافسة قفل بين المواضيع ، فسيؤدي ذلك إلى استهلاك إضافي لإلغاء القفل. | مناسبة للسيناريوهات حيث يصل مؤشر ترابط واحد فقط إلى الكتلة المتزامنة. |
قفل خفيف الوزن | لن تمنع المواضيع المتنافسة ، مما يحسن سرعة استجابة البرنامج. | إذا كان الخيط الذي لا يحصل على مسابقة القفل سيستهلك وحدة المعالجة المركزية. | متابعة وقت الاستجابة. تنفيذ كتلة متزامنة سريعة للغاية. |
قفل الوزن الثقيل | لا تستخدم مسابقة الموضوع تدور ولا تستهلك وحدة المعالجة المركزية. | حظر الموضوع ، وقت الاستجابة البطيء. | متابعة الإنتاجية. سرعة تنفيذ كتلة التزامن طويلة نسبيا. |