باعتبارها أداة لمشاركة البيانات بشكل متزامن وضمان الاتساق، فإن الأقفال لها تطبيقات متعددة على منصة JAVA (مثل المزامنة وReentrantLock، وما إلى ذلك). توفر هذه الأقفال المكتوبة بالفعل الراحة لتطويرنا، ولكن نادرًا ما يتم ذكر الطبيعة المحددة للأقفال ونوعها. ستقوم هذه السلسلة من المقالات بتحليل أسماء الأقفال الشائعة وخصائصها ضمن JAVA للإجابة على أسئلتك.
1. قفل الدوران
يتم تنفيذ أقفال الدوران من خلال السماح للخيط الحالي بالتنفيذ بشكل مستمر داخل جسم الحلقة فقط عندما يتم تغيير شروط الحلقة بواسطة سلاسل رسائل أخرى، يمكن إدخال القسم الحرج. انسخ الكود كما يلي:
الطبقة العامة SpinLock {
Private AtomicReference<Thread>sign =new AtomicReference<>();
قفل الفراغ العام (){
الموضوع الحالي = Thread.currentThread();
بينما(!sign .compareAndSet(null,current)){
}
}
فتح الفراغ العام (){
الموضوع الحالي = Thread.currentThread();
تسجيل .compareAndSet(current, null);
}
}
باستخدام العمليات الذرية لـ CAS، تقوم وظيفة القفل بتعيين المالك على الخيط الحالي وتتنبأ بأن القيمة الأصلية فارغة. تقوم وظيفة إلغاء القفل بتعيين المالك على القيمة الخالية، والقيمة المتوقعة هي مؤشر الترابط الحالي.
عندما يستدعي خيط آخر عملية القفل، لأن قيمة المالك ليست فارغة، يتم تنفيذ الحلقة حتى يستدعي الخيط الأول وظيفة إلغاء القفل لتعيين المالك على قيمة فارغة، ويمكن للخيط الثاني الدخول إلى القسم الحرج.
نظرًا لأن قفل الدوران يحافظ فقط على الخيط الحالي الذي ينفذ جسم الحلقة دون تغيير حالة الخيط، فإن سرعة الاستجابة تكون أسرع. ولكن عندما يستمر عدد الخيوط في الزيادة، ينخفض الأداء بشكل ملحوظ لأن كل سلسلة رسائل تحتاج إلى التنفيذ وتستهلك وقت وحدة المعالجة المركزية. إذا لم تكن منافسة الخيط شديدة وتم الحفاظ على القفل لفترة من الوقت. مناسبة للاستخدام مع أقفال الدوران.
ملاحظة: هذا المثال عبارة عن قفل غير عادل، ولن يعتمد ترتيب الحصول على القفل على ترتيب الدخول إلى القفل.
2. أنواع أخرى من أقفال الدوران
تحدثنا عن أقفال الدوران أعلاه. هناك ثلاثة أشكال قفل شائعة في أقفال الدوران: TicketLock، وCLHlock، وMCSlock.
يعمل قفل التذاكر بشكل أساسي على حل مشكلة تسلسل الوصول. المشكلة الرئيسية تكمن في وحدة المعالجة المركزية متعددة النواة:
انسخ رمز الكود كما يلي:
الحزمة com.alipay.titan.dcc.dal.entity؛
import java.util.concurrent.atomic.AtomicInteger;
قفل التذاكر للفئة العامة {
خدمة AtomicInteger الخاصة = new AtomicInteger();
تذكرة AtomicInteger الخاصة = new AtomicInteger();
نهائي ثابت خاص ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
قفل الفراغ العام () {
int myticket = TicketNum.getAndIncrement();
LOCAL.set(myticket);
بينما (myticket! = ServiceNum.get()) {
}
}
فتح الفراغ العام () {
int myticket = LOCAL.get();
ServiceNum.compareAndSet(myticket, myticket + 1);
}
}
يجب الاستعلام عن رقم خدمة ServiceNum في كل مرة، مما يؤثر على الأداء (يجب قراءته من الذاكرة الرئيسية ويجب منع وحدات المعالجة المركزية الأخرى من تعديله).
CLHLock وMCSLock هما نوعان متشابهان من الأقفال العادلة، مرتبة في شكل قائمة مرتبطة.
انسخ رمز الكود كما يلي:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
الطبقة العامة CLHLock {
فئة ثابتة عامة CLHNode {
القيمة المنطقية المتطايرة الخاصة isLocked = true؛
}
@SuppressWarnings("غير مستخدم")
ذيل CLHNode الخاص المتقلب؛
نهائي ثابت خاص ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
نهائي ثابت خاص AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
CLHNode.class, "tail");
قفل الفراغ العام () {
CLHNode = new CLHNode();
LOCAL.set(node);
CLHNode preNode = UPDATER.getAndSet(this,node);
إذا (عقدة مسبقة!= فارغة) {
بينما (preNode.isLocked) {
}
preNode = null;
LOCAL.set(node);
}
}
فتح الفراغ العام () {
عقدة CLHNode = LOCAL.get();
إذا (!UPDATER.compareAndSet(this,node, null)) {
Node.isLocked = false;
}
العقدة = فارغة؛
}
}
يقوم CLHlock بالاستعلام بشكل مستمر عن المتغيرات السابقة، مما يجعله غير مناسب للاستخدام ضمن بنية NUMA (في هذه البنية، يتم توزيع كل مؤشر ترابط في منطقة ذاكرة فعلية مختلفة)
حلقات MCSLock عبر عقد المتغيرات المحلية. لا توجد مشكلة مع CLHlock.
انسخ رمز الكود كما يلي:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
فئة عامة مكسلوك {
فئة ثابتة عامة MCSNode {
Volatile MCSNode التالي؛
القيمة المنطقية المتطايرة isLocked = true؛
}
نهائي ثابت خاص ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
@SuppressWarnings("غير مستخدم")
قائمة انتظار MCSNode الخاصة المتطايرة؛
نهائي ثابت خاص AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
MCSNode.class, "queue");
قفل الفراغ العام () {
MCSNode currentNode = new MCSNode();
NODE.set(currentNode);
MCSNode preNode = UPDATER.getAndSet(this,currentNode);
إذا (عقدة مسبقة != فارغة) {
preNode.next = currentNode;
بينما (currentNode.isLocked) {
}
}
}
فتح الفراغ العام () {
MCSNode currentNode = NODE.get();
إذا (currentNode.next == null) {
إذا (UPDATER.compareAndSet(this,currentNode, null)) {
} آخر {
بينما (currentNode.next == null) {
}
}
} آخر {
currentNode.next.isLocked = false;
currentNode.next = null;
}
}
}
من وجهة نظر الكود، يعد CLH أبسط من MCS.
قائمة انتظار CLH هي قائمة انتظار ضمنية ولا تحتوي على سمات عقدة لاحقة حقيقية.
قائمة انتظار MCS عبارة عن قائمة انتظار صريحة ذات سمات العقدة اللاحقة الحقيقية.
القفل الافتراضي المستخدم داخليًا بواسطة JUC ReentrantLock هو قفل CLH (هناك العديد من التحسينات، مثل استبدال أقفال الدوران بأقفال الحظر، وما إلى ذلك).
(ينتهي النص الكامل)