يحتوي Java5 بالفعل على أقفال القراءة والكتابة في حزمة java.util.concurrent. ومع ذلك ، يجب أن نفهم المبادئ الكامنة وراء تنفيذها.
تنفيذ جافا لقفل القراءة/الكتابة
دعنا أولاً نقدم نظرة عامة على شروط القراءة والكتابة إلى الموارد:
قراءة أي موضوع يقوم بعملية الكتابة ، ولا يوجد مؤشر ترابط يطلب عملية الكتابة.
لا يوجد موضوع يقوم بعمليات القراءة والكتابة.
إذا أراد مؤشر ترابط قراءة مورد ، طالما أنه لا يوجد مؤشر ترابط يكتب إلى المورد ولا يطلب مؤشر ترابط الكتابة إلى المورد. نحن نفترض أن طلبات عمليات الكتابة أكثر أهمية من طلبات عمليات القراءة ، لذلك يجب علينا زيادة أولوية طلبات الكتابة. بالإضافة إلى ذلك ، إذا حدثت عمليات القراءة بشكل متكرر ولم نزيد من أولوية عمليات الكتابة ، فسيحدث "الجوع". سيتم حظر الخيط الذي يطلب عملية الكتابة حتى يتم فتح جميع مؤشرات الترابط القراءة من ReadWritelock. إذا كانت أذونات عملية القراءة للمعلومات الجديدة مضمونة دائمًا ، فسيستمر مؤشر الترابط في انتظار عملية الكتابة في حظره ، وستكون النتيجة "الجوع". لذلك ، لا يمكن ضمان عملية القراءة إلا للمتابعة عندما لا يوجد مؤشر ترابط في قفل ReadWritelock لعمليات الكتابة ، ولا يطلب أي مؤشر ترابط أن يكون القفل جاهزًا لعمليات الكتابة.
عندما لا تقرأ مؤشرات الترابط الأخرى أو تكتب العمليات على المورد المشترك ، يجوز لخيط الحصول على قفل كتابة للمورد المشترك ثم كتابة العمليات على المورد المشترك. لا يهم عدد قوائم الرسائل التي تطلب أقفال الكتابة وبأي ترتيب ، إلا إذا كنت ترغب في ضمان عدالة طلب قفل الكتابة.
وفقًا للوصف أعلاه ، يتم تنفيذ قفل القراءة/الكتابة ببساطة ، والرمز كما يلي
الفئة العامة readWritelock {private int keners = 0 ؛ كتاب int الخاص = 0 ؛ int private interequests = 0 ؛ رمي lockread void synchronized () interruptedException {بينما (الكتاب> 0 || writeRequests> 0) {wait () ؛ } القراء ++ ؛ } inflockRead () {القراء-؛ إخطار () ؛ } رميات قفل الفراغ المزامنة العامة () interruptedException {writeRequests ++ ؛ بينما (القراء> 0 || الكتاب> 0) {wait () ؛ } writeRequests-- ؛ كتاب ++ ؛ ) إخطار () ؛ }}في فئة ReadWritelock ، اقرأ القفل وكتابة كل منهما طريقة لاكتساب القفل وإطلاقه.
تنفيذ قفل القراءة في LockRead (). طالما لا يوجد مؤشر ترابط قفل كتابة (كتاب == 0) ولا يوجد مؤشر ترابط يطلب قفل كتابة (WritereQuests == 0) ، يمكن الحصول على جميع مؤشرات الترابط التي تريد الحصول على قفل قراءة بنجاح.
تنفيذ القفل الكتابة في LockWrite (). عندما يرغب مؤشر ترابط في الحصول على قفل كتابة ، سيضيف أولاً 1 إلى رقم طلب القفل للكتابة (WritereQuests ++) ، ثم تحديد ما إذا كان بإمكانه حقًا الحصول على قفل كتابة. عندما لا يحتفظ أي مؤشر ترابط بقفل قراءة (القراء == 0) ولا يوجد مؤشر ترابط يحمل قفل كتابة (كتاب == 0) يمكنك الحصول على قفل كتابة. لا يهم عدد المواضيع التي تطلب كتابة الأقفال.
تجدر الإشارة إلى أنه في كل من UnlockRead ، UnlockWrite ، يتم استدعاء طريقة الإخطار بدلاً من الإخطار. لشرح هذا السبب ، يمكننا أن نتخيل الموقف التالي:
إذا كان الخيط ينتظر الحصول على قفل القراءة ، وينتظر الخيط للحصول على قفل الكتابة. إذا تم إيقاظ أحد المواضيع التي تنتظر قفل القراءة بواسطة طريقة الإخطار ، ولكن نظرًا لأنه لا يزال هناك مؤشر ترابط يطلب قفل الكتابة (WriteRequests> 0) ، فسيقوم الخيط المستيقظ بإدخال حالة الحظر مرة أخرى. ومع ذلك ، لم يتم إيقاظ أي من المواضيع التي تنتظر قفل الكتابة ، كما لو لم يحدث شيء (ملاحظة المترجم: فقدان الإشارة). إذا كنت تستخدم طريقة الإخطار ، فسيتم إيقاظ جميع مؤشرات الترابط ومن ثم تحديد ما إذا كان بإمكانهم الحصول على القفل الذي طلبته.
هناك أيضًا فائدة واحدة لاستخدام الإخطار. إذا كانت مؤشرات الترابط القراءة المتعددة تنتظر قفل القراءة ولم ينتظر أي مؤشر ترابط قفل الكتابة ، بعد استدعاء UnlockWrite () ، يمكن لجميع مؤشرات الترابط التي تنتظر قفل القراءة الحصول على الفور على الفور على قفل القراءة - بدلاً من واحد فقط في وقت واحد.
إعادة إدخال قفل القراءة/الكتابة
قفل القراءة/الكتابة المنفذة أعلاه ليس إعادة إدخاله وسيتم حظره عندما يطلب مؤشر ترابط يحمل بالفعل قفل الكتابة قفل الكتابة مرة أخرى. والسبب هو أن هناك بالفعل موضوع كتابة - إنه في حد ذاته. أيضا ، النظر في المثال التالي:
من أجل جعل ReadWritelock reentable ، يجب إجراء بعض التحسينات عليها. سيتعامل فيما يلي مع إعادة إدخال قفل القراءة وإعادة إدخال قفل الكتابة على التوالي.
اقرأ القفل Reenter
من أجل جعل قفل قراءة ReadWritelock Reenrant ، يجب علينا أولاً تحديد قواعد لقفل القراءة Reenrant:
للتأكد من إعادة إدخال قفل القراءة في مؤشر ترابط ، إما تلبي الشروط للحصول على قفل القراءة (بدون طلب كتابة أو كتابة) أو امسك بالفعل قفل القراءة (بغض النظر عما إذا كان هناك طلب كتابة أم لا). لتحديد ما إذا كان مؤشر ترابط قد احتفظ بالفعل بقفل قراءة ، يمكن استخدام خريطة لتخزين الخيط الذي احتفظ بالفعل بقفل قراءة وعدد المرات التي يكتسب فيها مؤشر الترابط المقابل قفل قراءة. عندما يكون من الضروري تحديد ما إذا كان بإمكان مؤشر ترابط الحصول على قفل قراءة ، يتم استخدام البيانات المخزنة في الخريطة لإصدار حكم. ما يلي هو الكود المعدل للطرق LockRead و UnlockRead:
الفئة العامة readWritelock {Private Map <Thread ، integer> readingThreads = new HashMap <thread ، integer> () ؛ كتاب int الخاص = 0 ؛ int private interequests = 0 ؛ رمي lockRead void المتزامنة العامة () interruptedException {thread callthread = thread.currentThread () ؛ بينما (! congrantreadAccess (callingThread)) {wait () ؛ } readingThreads.put (callingThread ، (getAccessCount (callingThread) + 1)) ؛ } profid inflockRead () {thread callthread = thread.currentThRead () ؛ int AccessCount = getAccessCount (callingThread) ؛ if (AccessCount == 1) {readingThreads.Remove (callingThread) ؛ } else {readingThreads.put (callingThread ، (AccessCount -1)) ؛ } إخطار () ؛ } private boolean cangrantreadAccess (thread callingThread) {if (writers> 0) return false ؛ إذا كانت (isReader (callthread) return true ؛ if (writeRequests> 0) return false ؛ return true ؛} private int getReadAccessCount (thread callingthread) {Integer AccessCount = ReadingThreads.get (callingthread) ؛ if (accessCount == null) ReadingThreads.get (CallingThread)! = NULL ؛في الكود ، يمكننا أن نرى أن إعادة إدخال أقفال القراءة مسموح بها فقط إذا لم يكن لدى مؤشر ترابط قفل كتابة. بالإضافة إلى ذلك ، فإن أقفال قراءة Reentrant لها أولوية أعلى من أقفال الكتابة.
اكتب قفل إعادة reenter
لا يُسمح بإعادة الدخول إلى قفل الكتابة إلا إذا كان موضوعًا يحمل بالفعل قفل كتابة (استعد قفل الكتابة). ما يلي هو الكود المعدل للطرق LockWrite و UnlockWrite.
الفئة العامة readWritelock {Private Map <Thread ، integer> readingThreads = new HashMap <thread ، integer> () ؛ private int writeaccesses = 0 ؛ int private interequests = 0 ؛ thread thindtherthread = null ؛ رميات lockwrite المزامنة العامة () interruptedException {writerequests ++ ؛ CallingTherThread = thread.currentThRead () ؛ بينما (! cangrantWriteAccess (callingThread)) {wait () ؛ } writeRequests-- ؛ WriteAccesses ++ ؛ الكتابة = callingThread ؛ ) if (writeAccesses == 0) {trintingThread = null ؛ } إخطار () ؛ } private boolean cangrantwriteaccess (thread callingthread) {if (hasreaders ()) return false ؛ إذا كان (trintingThread == NULL) يعود صحيح ؛ إذا (! isWriter (callingThread)) العودة كاذبة ؛ العودة صحيح. } private boolean hasreaders () {return readingThreads.size ()> 0 ؛ } iswriter boolean private (thread callingthread) {return trintingThread == callingThread ؛ }}انتبه إلى كيفية التعامل معها عند تحديد ما إذا كان الخيط الحالي يمكنه الحصول على قفل الكتابة.
قراءة قفل ترقية لكتابة القفل
في بعض الأحيان ، نريد موضوعًا يحتوي على قفل قراءة للحصول على قفل كتابة. للسماح بمثل هذه العمليات ، يلزم أن يكون هذا الموضوع هو الخيط الوحيد مع قفل قراءة. يحتاج Writelock () إلى إجراء بعض التغييرات لتحقيق هذا الهدف:
الفئة العامة readWritelock {Private Map <Thread ، integer> readingThreads = new HashMap <thread ، integer> () ؛ private int writeaccesses = 0 ؛ int private interequests = 0 ؛ thread thindtherthread = null ؛ رميات lockwrite المزامنة العامة () interruptedException {writerequests ++ ؛ CallingTherThread = thread.currentThRead () ؛ بينما (! cangrantWriteAccess (callingThread)) {wait () ؛ } writeRequests-- ؛ WriteAccesses ++ ؛ الكتابة = callingThread ؛ ) if (writeAccesses == 0) {trintingThread = null ؛ } إخطار () ؛ } private boolean cangrantwriteaccess (thread callingThread) {if (isonlyReader (callingthread)) return true ؛ إذا كان (hasreaders ()) يعود خطأ ؛ إذا كان (trintingThread == NULL) يعود صحيح ؛ إذا (! isWriter (callingThread)) العودة كاذبة ؛ العودة صحيح. } private boolean hasreaders () {return readingThreads.size ()> 0 ؛ } iswriter boolean private (thread callingthread) {return trintingThread == callingThread ؛ } boolean private isonlyReader (Thread Thread) {return readers == 1 && readingThreads.get (callingThread)! = null ؛ }}الآن يمكن ترقية فئة ReadWritelock من قفل قراءة إلى قفل الكتابة.
اكتب القفل لقراءة القفل
في بعض الأحيان ، تريد الخيوط التي تحتوي على أقفال الكتابة الحصول على أقفال القراءة. إذا كان مؤشر الترابط يحتوي على قفل كتابة ، فلن تتمكن مؤشرات الترابط الأخرى بشكل طبيعي من قفل قراءة أو قفل كتابة. لذلك ، لا يوجد خطر على موضوع يحتوي على قفل كتابة ثم يحصل على قفل قراءة. نحتاج فقط إلى إجراء تعديل بسيط لأسلوب CanGranTreadAccess أعلاه:
الفئة العامة readWritelock {private boolean cangrantreadaccess (thread callingThread) {if (isWriter (callthread)) return true ؛ إذا (الكتابة! = فارغة) العودة كاذبة ؛ إذا كان (isReader (callthread) يعود صحيح ؛ إذا (writeRequests> 0) ، فإن إرجاع خطأ ؛ إرجاع صحيح ؛}}تنفيذ كامل لـ Reentrant ReadWritelock
فيما يلي تطبيق ReadWritelock الكامل. من أجل تسهيل قراءة وفهم الكود ، تم إعادة إعادة تمثيل الكود أعلاه. الرمز الذي تم إعادة تمهيده كما يلي.
الفئة العامة readWritelock {Private Map <Thread ، integer> readingThreads = new HashMap <thread ، integer> () ؛ private int writeaccesses = 0 ؛ int private interequests = 0 ؛ thread thindtherthread = null ؛ رمي lockRead void المتزامنة العامة () interruptedException {thread callthread = thread.currentThread () ؛ بينما (! congrantreadAccess (callingThread)) {wait () ؛ } readingThreads.put (callingThread ، (getReadAccessCount (callthread) + 1)) ؛ } private boolean cangrantreadaccess (thread callingThread) {if (isWriter (callingThread)) return true ؛ إذا كان (Haswriter ()) يعود خطأ ؛ إذا كان (Isreader (CalltHread)) يعود True ؛ if (haswriteRequests ()) العودة false ؛ العودة صحيح. } profid inflockRead () {thread callthread = thread.currentThRead () ؛ if (! isReader (CallTherThread)) {رمي جديد inchlotiTitorStateException ("Calling Thread لا" + "عقد قفل قراءة على هذا القراءة") ؛ } int accessCount = getReadAccessCount (callingThread) ؛ if (AccessCount == 1) {readingThreads.Remove (callingThread) ؛ } else {readingThreads.put (callingThread ، (AccessCount -1)) ؛ } إخطار () ؛ } رميات قفل الفراغ المزامنة العامة () interruptedException {writeRequests ++ ؛ CallingTherThread = thread.currentThRead () ؛ بينما (! cangrantWriteAccess (callingThread)) {wait () ؛ } writeRequests-- ؛ WriteAccesses ++ ؛ الكتابة = callingThread ؛ ) Boolean CanngrantWriteAccess (Thread Calling) {if (isonlyreader) إذا كان AccessCount == NULL ؛ && readingThreads.get (callingThread)! = null ؛ } private boolean haswriter () {return writingthread! = null ؛ } iswriter boolean private (thread callingthread) {return trintingThread == callingThread ؛ } boolean private haswriteRequests () {return this.writeRequests> 0 ؛ }}استدعاء فتح () في النهاية
عند استخدام ReadWritelock لحماية المناطق الحرجة ، إذا كانت المنطقة الحرجة قد ترمي استثناء ، فمن المهم استدعاء readUnlock () و trintunlock () في الكتلة الأخيرة. يتم ذلك لضمان إمكانية فتح ReadWritelock بنجاح ، ويمكن أن تطلب مؤشرات الترابط الأخرى القفل. هنا مثال:
lock.lockwrite () ؛ جرب {// do critic section code ، والذي قد يرمي استثناء} أخيرًا {lock.unlockwrite () ؛}يمكن أن تضمن بنية الكود أعلاه أن القراءة سيتم إصدارها أيضًا عند إلقاء استثناء في المنطقة الحرجة. إذا لم يتم استدعاء طريقة UnlockWrite في الكتلة الأخيرة ، عندما يتم إلقاء استثناء في القسم الحرج ، فسيبقى ReadWritelock في حالة قفل الكتابة ، مما سيؤدي إلى حظر جميع مؤشرات الترابط القفل () أو LockWrite (). قد يكون العامل الوحيد الذي يمكن أن يعيد إجراء عملية إعادة القراءة هو أن ReadWritelock هو إعادة إدخال. عند إلقاء استثناء ، يمكن للمعلومات الحصول على القفل بنجاح ، ثم تنفيذ القسم الحرج واتصل UnlockWrite () مرة أخرى ، والذي سيصدر ReadWritelock مرة أخرى. ولكن ماذا لو لم يعد الموضوع يكتسب القفل؟ لذلك ، فإن استدعاء UnlockWrite في النهاية أمر مهم للغاية لكتابة رمز قوي.
ما سبق هو مجموعة من معلومات جافا متعددة الخيوط. سنستمر في إضافة المعلومات ذات الصلة في المستقبل. شكرا لدعمكم لهذا الموقع!