في [Concrency High Concurrency Java II] متعددة الخيوط ، ذكرنا في البداية عمليات مزامنة الخيط الأساسية. ما نريد ذكره هذه المرة هو أداة التحكم في المزامنة في الحزمة المتزامنة.
1. استخدام أدوات التحكم في التزامن المختلفة
1.1 reentrantlock
REENTRANTLOCK يبدو وكأنه نسخة محسنة من متزامن. ميزة المزامنة هي أنها بسيطة الاستخدام وأن كل شيء يترك إلى JVM للمعالجة ، ولكن وظائفها ضعيفة نسبيًا. قبل JDK1.5 ، كان أداء REENTRANTLOCK أفضل من مزامنة. نظرًا لتحسين JVM ، فإن أداء الاثنين في إصدار JDK الحالي قابل للمقارنة. إذا كان تطبيقًا بسيطًا ، فلا تستخدم عن عمد إعادة إيندرانتلوك.
بالمقارنة مع متزامن ، فإن إعادة إيندرانتلوك هي أكثر ثراءً من الناحية الوظيفية ، ولديها خصائص إعادة إدخالها ، قابلة للمقاطعة ، وقت محدود ، وقفل عادل.
أولاً ، دعونا نستخدم مثالًا لتوضيح الاستخدام الأولي لـ REENTRANTLOCK:
اختبار الحزمة ؛ استيراد java.util.concurrent.locks.reentrantlock ؛ اختبار الطبقة العامة يطبق Runnable {public reentrantlock lock = new reentrantlock () ؛ int static int i = 0 ؛ Override public void run () {for (int j = 0 ؛ j <10000000 ؛ j ++) {lock.lock () ؛ حاول {i ++ ؛ } أخيرًا {lock.unlock () ؛ }}} رميات الفراغ الثابتة العامة (سلسلة [] args) interruptedException {test test = new test () ؛ الموضوع T1 = موضوع جديد (اختبار) ؛ الموضوع T2 = موضوع جديد (اختبار) ؛ t1.start () ؛ t2.start () ؛ t1.join () ؛ t2.join () ؛ system.out.println (i) ؛ }}هناك نوعان من المواضيع التي تؤدي عمليات ++ على i. من أجل ضمان سلامة الخيط ، يتم استخدام إعادة إيندرانتيلوك. من الاستخدام ، يمكننا أن نرى أنه بالمقارنة مع المتزامنة ، فإن REENTRANTLOCK أكثر تعقيدًا بقليل. نظرًا لأنه يجب إجراء عملية إلغاء القفل في النهاية ، إذا لم يتم إلغاء قفلها أخيرًا ، فمن المحتمل أن يكون الرمز استثناء ولم يتم إصدار القفل ، ويتم إصدار مزامنة بواسطة JVM.
إذن ما هي الخصائص الممتازة لإعادة الدخول؟
1.1.1 إعادة الدخول
يمكن إدخال موضوع واحد مرارًا وتكرارًا ، ولكن يجب الخروج مرارًا وتكرارًا
lock.lock () ؛ lock.lock () ؛ حاول {i ++ ؛ } أخيرًا {lock.unlock () ؛ lock.unlock () ؛}نظرًا لأن REENTRANTLOCK عبارة عن قفل إعادة إدخال ، يمكنك الحصول على نفس القفل مرارًا وتكرارًا ، والذي يحتوي على عداد اكتساب متعلق بالقفل. إذا حصل مؤشر ترابط يمتلك القفل مرة أخرى ، فسيتم زيادة عداد الاستحواذ بمقدار 1 ، ويجب إصدار القفل مرتين للحصول على الإصدار الحقيقي (إعادة إدخال القفل). هذا يحاكي دلالات المزامنة. إذا دخل الخيط إلى كتلة متزامنة محمية بواسطة الشاشة التي يحتوي عليها الخيط بالفعل ، يُسمح للموضوع بالمتابعة. عندما يخرج الخيط من الكتلة المزامنة الثانية (أو اللاحقة) ، لا يتم إطلاق القفل. يتم إصدار القفل فقط عندما يخرج الخيط من أول كتلة متزامنة محمية بواسطة الشاشة التي يدخلها.
يمتد الطفل من الطبقة العامة ، يمتد الأب ، على قابلة للتشغيل {Final Static Child = New Child () ؛ // من أجل ضمان قفلات الفراغ الفريد الفريد الفريد الفريد (String [] args) {for (int i = 0 ؛ i <50 ؛ i ++) {new thread (child) .start () ؛ }} public synchronized void dosomething () {system.out.println ("1Child.Dosomething ()") ؛ doanotherthing () ؛ // استدعاء طرق متزامنة أخرى في فئتك الخاصة} void void doanotherthing () {super.dosomething () ؛ // استدعاء الطريقة المتزامنة لنظام الفئة الأصل. } Override public void run () {child.dosomething () ؛ }} class الأب {public synchronized void dosomething () {system.out.println ("2father.dosomething ()") ؛ }}يمكننا أن نرى أن مؤشر ترابط يدخل طريقة متزامنة مختلفة ولن يتم تحرير الأقفال التي تم الحصول عليها من قبل. لذلك لا يزال الإخراج بالتسلسل. لذلك تزامنه أيضًا قفل إعادة إدخال
الإخراج:
1child.dosomething ()
2father.dosomething ()
3child.doanotherthing ()
1child.dosomething ()
2father.dosomething ()
3child.doanotherthing ()
1child.dosomething ()
2father.dosomething ()
3child.doanotherthing ()
...
1.1.2. مقاطع
على عكس المزامنة ، فإن إعادة إيندرانتلوك تستجيب للمقاطعات. عرض المعرفة ذات الصلة [التزامن العالي جافا 2] أساسيات الرئاسة المتعددة
لا يمكن أن يستجيب القفل العادي.
نقوم بمحاكاة مشهد طريق مسدود ثم نستخدم المقاطعات للتعامل مع الجمود
اختبار الحزمة ؛ استيراد java.lang.management.managementfactory ؛ استيراد java.lang.management.threadinfo ؛ استيراد java.lang.management.ThreadMxbean ؛ استيراد java.util.concurrent.locks.reentrantlock ؛ اختبار فئة عامة تنطس {public reentrantlock lock1 = new reentrantrantlock () ؛ reentrantlock static static static = جديد reentrantlock () ؛ قفل int اختبار عام (int lock) {this.lock = lock ؛ } Override public void run () {try {if (lock == 1) {lock1.lockInterruptible () ؛ حاول {thread.sleep (500) ؛ } catch (استثناء e) {// todo: handle insport} lock2.lockInterruptilely () ؛ } آخر {lock2.lockInterruptilely () ؛ حاول {thread.sleep (500) ؛ } catch (استثناء e) {// todo: handle insport} lock1.lockInterruptible () ؛ }} catch (استثناء e) {// todo: handled expecte} أخيرًا {if (lock1.isheldbyCurrentThRead ()) {lock1.unlock () ؛ } if (lock2.isheldbyCurrentTrThread ()) {lock2.unlock () ؛ } system.out.println (thread.currentThRead (). getId () + ": thread exit") ؛ }} public static void main (string [] args) remrows interruptedException {test t1 = new test (1) ؛ اختبار T2 = اختبار جديد (2) ؛ Thread Thread1 = مؤشر ترابط جديد (T1) ؛ Thread Thread2 = مؤشر ترابط جديد (T2) ؛ thread1.start () ؛ thread2.start () ؛ thread.sleep (1000) ؛ //deadlockchecker.check () ؛ } static class deadlockChecker {private final static threadmxbean mbean = managementfactory .getThReadMxBean () ؛ Final Static Runnable DeadlockChecker = new RunNable () {Override public void run () {// todo method method method tuto بينما (صحيح) {long [] deadlockedThreadids = mbean.findDeadClockedThreads () ؛ if (deadlockedThreadids! = null) {threadInfo [] threadInfos = mbean.getThreadInfo (deadlockedThreadids) ؛ لـ (thread t: thread.getAllStackTraces (). keyset ()) {for (int i = 0 ؛ i <froofinfos.length ؛ i ++) {if (t.getId () == threadInfos [i] .getThReadId ()) {t.interrupt () ؛ }}}}} جرب {thread.sleep (5000) ؛ } catch (استثناء e) {// todo: مقبض الاستثناء}}}}}} ؛ check static static static check () {thread t = new thread (deadlockChecker) ؛ T.SetDaemon (صواب) ؛ T.Start () ؛ }}}قد يتسبب الكود أعلاه في حالة حدوث مسدود ، ويحصل الموضوع 1 على Lock1 ، ويحصل الموضوع 2 على Lock2 ، ثم يريد بعضهم البعض الحصول على أقفال بعضنا البعض.
نستخدم JSTACK لعرض الموقف بعد تشغيل الكود أعلاه
تم اكتشاف بالفعل طريق مسدود.
deadlockChecker.check () ؛ يتم استخدام الطريقة لاكتشاف الجمود ثم مقاطعة خيط adadlock. بعد الانقطاع ، يخرج الخيط بشكل طبيعي.
1.1.3. محدودة الوقت
إذا لم تتمكن المهلة من الحصول على القفل ، فسوف يعود خطأ ولن ينتظر بشكل دائم لتشكيل قفل ميت.
استخدم Lock.Trylock (فترة طويلة ، وحدة TimeUnit) لتنفيذ الأقفال القابلة للوصول إلى الوقت ، مع وجود معلمات الوقت والوحدات.
اسمحوا لي أن أعطيك مثالا لتوضيح أن الوقت يمكن أن يكون محدودا:
اختبار الحزمة ؛ استيراد java.util.concurrent.timeUnit ؛ استيراد java.util.concurrent.locks.reentrantlock ؛ اختبار الطبقة العامة ينفذ Runnable {قفل reentrantlock الثابت العام = جديد reentrantlock () ؛ Override public void run () {try {if (lock.trylock (5 ، timeUnit.seconds)) {thread.sleep (6000) ؛ } آخر {system.out.println ("Get Lock Favor") ؛ }} catch (استثناء e) {} أخيرًا {if (lock.isheldbyCurrentThread ()) {lock.unlock () ؛ }}} public static void main (string [] args) {test t = new test () ؛ الموضوع T1 = موضوع جديد (T) ؛ الموضوع T2 = مؤشر ترابط جديد (T) ؛ t1.start () ؛ t2.start () ؛ }}استخدم خيطين للتنافس على قفل. عندما يكتسب مؤشر ترابط القفل ، ينام 6 ثوانٍ ، ويحاول كل موضوع فقط الحصول على القفل لمدة 5 ثوانٍ.
لذلك يجب أن يكون هناك موضوع لا يمكن الحصول على القفل. إذا لم تتمكن من الحصول عليها ، فستخرج مباشرة.
الإخراج:
فشلت الحصول على القفل
1.1.4. قفل عادل
كيفية استخدام:
إعادة إدخال عام (معرض منطقي)
REENTRANTLOCK FAIRLOCK العام = جديد reentrantlock (صحيح) ؛
الأقفال بشكل عام غير عادل. ليس من الممكن بالضرورة أن يتمكن الخيط الذي يأتي أولاً من الحصول على القفل أولاً ، لكن الخيط الذي يأتي لاحقًا سيحصل على القفل لاحقًا. الأقفال غير العادلة يمكن أن تسبب الجوع.
يعني القفل العادل أن هذا القفل يمكن أن يضمن أن الخيط يأتي أولاً ويحصل على القفل أولاً. على الرغم من أن الأقفال العادلة لن تسبب الجوع ، إلا أن أداء الأقفال العادلة سيكون أسوأ بكثير من أقفال غير المقدمة.
1.2 الحالة
تشبه العلاقة بين الحالة و reentrantlock المتزامنة والكائن. wait ()/signal ()
ستجعل طريقة AWAIT () الخيط الحالي الانتظار وإطلاق القفل الحالي. عند استخدام الإشارة () في مؤشرات الترابط الأخرى أو طريقة SignalAll () ، سوف يستعيد مؤشر الترابط القفل ويستمر في التنفيذ. أو عند مقاطعة الخيط ، يمكنك أيضًا القفز من الانتظار. هذا يشبه إلى حد كبير طريقة الكائن. wait ().
طريقة Awaituninterruptluredruptreruptruptreruptruptruptruptruptruction هي نفس طريقة AWAIT () ، لكنها لن تنتظر مقاطعة الاستجابة أثناء العملية. يتم استخدام طريقة Singal () لإيقاظ موضوع في انتظار. ستستيقظ طريقة Singalall () النسبية Singalall (). هذا يشبه إلى حد كبير طريقة OBJCT.Notify ().
لن أقدمه بالتفصيل هنا. اسمحوا لي أن أقدم لك مثالا لتوضيح:
اختبار الحزمة ؛ استيراد java.util.concurrent.locks.condition ؛ import java.util.concurrent.locks.reentrantlock ؛ اختبار الطبقة العامة ينفذ Runnable {قفل reentrantlock الثابت العام = جديد reentrantlock () ؛ حالة الحالة الثابتة العامة = lock.newcondition () ؛ Override public void run () {try {lock.lock () ؛ حالة. System.out.println ("Thread is on") ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ } أخيرًا {lock.unlock () ؛ }} public static void main (string [] args) remrows interruptedException {test t = new test () ؛ موضوع الموضوع = مؤشر ترابط جديد (T) ؛ thread.start () ؛ thread.sleep (2000) ؛ lock.lock () ؛ حالة. lock.unlock () ؛ }}المثال أعلاه بسيط للغاية. دع الخيط ينتظر ودع الموضوع الرئيسي يوقظه. حالة.
1.3.semaphore
بالنسبة للأقفال ، فهو حصري بشكل متبادل. هذا يعني أنه طالما أحصل على القفل ، لا يمكن لأحد الحصول عليه مرة أخرى.
بالنسبة لسيارة الإشارة ، فإنه يسمح لخيوط متعددة بدخول القسم الحرج في نفس الوقت. يمكن اعتباره قفلًا مشتركًا ، لكن الحد المشترك محدود. بعد استخدام الحد الأقصى ، ستظل مؤشرات الترابط الأخرى التي لم تحصل على الحد خارج المنطقة الحرجة. عندما يكون المبلغ 1 ، فإنه يعادل القفل
هنا مثال:
اختبار الحزمة ؛ استيراد java.util.concurrent.executorservice ؛ استيراد java.util.concurrent.executors ؛ استيراد java.util.concurrent.semaphore ؛ اختبار الطبقة العامة يبرز Runnable {Final Smaphore = new Semaphore (5) ؛ Override public void run () {try {semaphore.acquire () ؛ thread.sleep (2000) ؛ system.out.println (thread.currentThRead (). getId () + "done") ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ } أخيرًا {semaphore.Release () ؛ }} public static void main (string [] args) remrows interruptedException {executorService ExecutorService = Executors.NewFixedThreadPool (20) ؛ الاختبار النهائي t = اختبار جديد () ؛ لـ (int i = 0 ؛ i <20 ؛ i ++) {executorService.Submit (t) ؛ }}}يوجد تجمع مؤشرات ترابط يحتوي على 20 موضوعًا ، ويذهب كل مؤشر ترابط إلى ترخيص Semaphore. لا يوجد سوى 5 تراخيص لـ Semaphore. بعد التشغيل ، يمكنك أن ترى أن 5 مخرجات على دفعات ، يتم إخراج الدفعات.
بالطبع ، يمكن أن يتقدم مؤشر ترابط واحد أيضًا للحصول على تراخيص متعددة مرة واحدة
الحصول على الفراغ العام (تصاريح INT) رميات المقاطعات
1.4 ReadWritelock
ReadWritelock هو قفل يميز الوظائف. القراءة والكتابة هي وظيفتان مختلفتان: قراءة القراءة ليست حصرية بشكل متبادل ، فالقراءة حصرية بشكل متبادل ، وكتابة الكتابة حصرية بشكل متبادل.
يزيد هذا التصميم من التزامن ويضمن أمان البيانات.
كيفية استخدام:
private reentrantreadwritelock readwritelock = جديد reentrantreadwritelock () ؛
قفل ثابت خاص readlock = readWritelock.readlock () ؛
قفل ثابت خاص writelock = readWritelock.Writelock () ؛
للحصول على أمثلة مفصلة ، يمكنك عرض تنفيذ Java لمشاكل المنتج والمستهلك ومشاكل القارئ والكاتب ، ولن أقوم بتوسيعه هنا.
1.5 العد التنازلي
سيناريو نموذجي لمؤقت العد التنازلي هو إطلاق صاروخ. قبل إطلاق الصاروخ ، من أجل ضمان أن يكون كل شيء مضمونًا ، وغالبًا ما يتم إجراء عمليات تفتيش من المعدات والأدوات المختلفة. لا يمكن للمحرك إشعال إلا بعد اكتمال جميع عمليات التفتيش. هذا السيناريو مناسب جدًا لقرص التراجع. يمكن أن يجعل مؤشر ترابط الاشتعال انتظر حتى يكمل جميع مؤشرات الترابط
كيفية استخدام:
static النهائي countdownlatch end = new CountdownLatch (10) ؛
end.countdown () ؛
end.await () ؛
رسم تخطيطي:
مثال بسيط:
اختبار الحزمة ؛ استيراد java.util.concurrent.countdownlatch ؛ import java.util.concurrent.executorservice ؛ import java.util.concurrent.executors ؛ اختبار الفئة العامة يبرز Runnable {Static Final Countdownlatchlatch = New CountdownLatch (10) ؛ اختبار نهائي ثابت t = اختبار جديد () ؛ Override public void run () {try {thread.sleep (2000) ؛ System.out.println ("Complete") ؛ CountDownlatch.countdown () ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ }} public static void main (string [] args) remrows interruptedException {ExecutorService ExecutorService = Executors.NewFixedThreadPool (10) ؛ لـ (int i = 0 ؛ i <10 ؛ i ++) {executorService.execute (t) ؛ } countDownLatch.await () ؛ system.out.println ("end") ؛ ExecutorService.shutdown () ؛ }}يجب أن ينتظر مؤشر الترابط الرئيسي حتى يتم تنفيذ جميع مؤشرات الترابط العشرة قبل إخراج "النهاية".
1.6 Cyclicbarrier
على غرار CountDownlatch ، فإنه ينتظر أيضًا إكمال بعض المواضيع قبل تنفيذها. الفرق مع CountDownlatch هو أنه يمكن استخدام هذا العداد بشكل متكرر. على سبيل المثال ، لنفترض أننا قمنا بتعيين العداد على 10. ثم بعد جمع الدفعة الأولى من 10 خيوط ، سيعود العداد إلى الصفر ، ثم جمع الدفعة التالية من 10 مؤشرات ترابط
كيفية استخدام:
Cyclicbarrier العام (الأطراف int ، Runnable BarrierAction)
BarrierAction هو الإجراء الذي سيؤديه النظام عندما يتم حساب العداد مرة واحدة.
في انتظار ()
رسم تخطيطي:
هنا مثال:
اختبار الحزمة ؛ استيراد java.util.concurrent.cyclicbarrier ؛ اختبار الطبقة العامة يطرف Runnable {private String Soldier ؛ Cyclicbarrier Cyclic الخاص ؛ اختبار عام (سلسلة جندي ، Cyclicbarrier Cyclic) {this.soldier = soldier ؛ this.cyclic = دوري ؛ } Override public void run () {try {// انتظر جميع الجنود للوصول إلى cyclic.await () ؛ Dowork () ؛ // انتظر لجميع الجنود لإكمال عملهم Cyclic.await () ؛ } catch (استثناء e) {// todo catch e.printstacktrace () ؛ }} private void dowork () {// todo method method method tuto town {thread.sleep (3000) ؛ } catch (استثناء e) {// todo: handled expecte} system.out.println (soldier + ": done") ؛ } Public Static Class Barrierrun ينفذ Runnable {boolean flag ؛ int n ؛ Barrierrun (العلم المنطقي ، int n) {super () ؛ this.flag = flag ؛ this.n = n ؛ } Override public void run () {if (flag) {system.out.println (n + "expression task") ؛ } آخر {system.out.println (n + "SET expension") ؛ العلم = صحيح ؛ }}} public static void main (string [] args) {final int n = 10 ؛ الموضوع [] المواضيع = مؤشر ترابط جديد [n] ؛ العلم المنطقي = خطأ ؛ Cyclicbarrier Barrier = New Cyclicbarrier (N ، New Barrierrun (flag ، n)) ؛ system.out.println ("set") ؛ لـ (int i = 0 ؛ i <n ؛ i ++) {system.out.println (i+"report") ؛ المواضيع [i] = موضوع جديد (اختبار جديد ("Soldier" + I ، Barrier)) ؛ المواضيع [i] .start () ؛ }}}نتيجة الطباعة:
يجتمع
0 تقارير
1 تقرير
2 تقارير
3 تقارير
4 تقارير
5 تقارير
6 تقارير
7 تقارير
8 تقارير
9 تقارير
10 مجموعات كاملة الجندي 5: تم
الجندي 7: تم
الجندي 8: تم
الجندي 3: تم
الجندي 4: تم
الجندي 1: تم
الجندي 6: تم
الجندي 2: تم
الجندي 0: تم
الجندي 9: تم
10 مهام الانتهاء
1.7 Locksupport
توفير خيط حظر بدائي
على غرار تعليق
locksupport.park () ؛
locksupport.unpark (T1) ؛
بالمقارنة مع تعليق ، ليس من السهل التسبب في تجميد الخيط.
فكرة Locksupport تشبه إلى حد ما semaphore. لديها ترخيص داخلي. يسلب هذا الترخيص عند إيقافه ويتقدم للحصول على هذا الترخيص عند unpark. لذلك ، إذا كان Unpark قبل الحديقة ، فلن يحدث تجميد الخيط.
الرمز التالي هو رمز عينة التعليق في [Concrency High Concrency Java 2] متعددة الخيوط. يحدث adadlock عند استخدام تعليق.
اختبار الحزمة ؛ استيراد java.util.concurrent.locks.locksupport ؛ اختبار الفئة العامة {static object u = new Object () ؛ TestSusSpendThread T1 = New TestSusPendThread ("T1") ؛ TestSusSpendThread T2 = New TestSusPendThread ("T2") ؛ تمديد اختبار الفئة الثابتة العامة TestSusPendThread Thread {Public TestSusPendThread (اسم السلسلة) {setName (name) ؛ } Override public void run () {synchronized (u) {system.out.println ("in" + getName ()) ؛ //Thread.CurrentThRead (). تعليق () ؛ locksupport.park () ؛ }}} رميات الفراغ الثابتة العامة (سلسلة [] args) interruptedException {t1.start () ؛ thread.sleep (100) ؛ t2.start () ؛ // t1.resume () ؛ // t2.resume () ؛ locksupport.unpark (T1) ؛ locksupport.unpark (t2) ؛ t1.join () ؛ t2.join () ؛ }}ومع ذلك ، فإن استخدام Locksupport لن يسبب مسدود.
فضلاً عن ذلك
يمكن أن يستجيب Park () للمقاطعات ، ولكنه لا يرمي استثناءات. نتيجة استجابة المقاطعة هي أن عودة وظيفة Park () يمكن أن تحصل على علامة المقاطعة من Thread.Interrupted ().
هناك العديد من الأماكن في JDK التي تستخدم Park ، بطبيعة الحال ، يتم تنفيذ تطبيق Locksupport باستخدام غير آمن. park ().
بارك الفراغ الثابت العام () {
غير آمن. park (خطأ ، 0L) ؛
}
1.8 تنفيذ REENTRANTLOCK
دعونا نقدم تنفيذ reentrantlock. يتألف تنفيذ reentrantlock بشكل أساسي من ثلاثة أجزاء:
سيكون لدى فئة الوالدين في Reentrantlock متغير حالة لتمثيل الحالة المتزامنة.
/*** حالة التزامن. */ الحالة المتطايرة الخاصة ؛
اضبط الحالة لاكتساب القفل من خلال عملية CAS. إذا تم ضبطه على 1 ، فسيتم إعطاء حامل القفل إلى الخيط الحالي
قفل void النهائي () {if (compareAndSetState (0 ، 1)) setExClusiveOwnerThread (thread.currentThRead ()) ؛ آخر الحصول على (1) ؛ }إذا لم ينجح القفل ، فسيتم تقديم تطبيق
الفراغ النهائي العام الحصول على (int arg) {if (! tryAcquire (arg) && custirequeued (addwaiter (node.exclusive) ، arg)) selfinterrupt () ؛ }أولاً ، حاول TryAcquire بعد التقديم ، لأن موضوع آخر قد يكون قد أصدر القفل.
إذا كنت لا تزال لم تتقدم بطلب للحصول على القفل ، أضف النادل ، مما يعني إضافة نفسك إلى قائمة انتظار الانتظار
عقدة خاصة addwaiter (وضع العقدة) {node node = new node (thread.currentThRead () ، mode) ؛ // جرب المسار السريع لـ ENQ ؛ النسخ الاحتياطي إلى ENQ الكامل على عقدة الفشل pred = tail ؛ if (pred! = null) {node.prev = pred ؛ if (compareAndSetTail (pred ، node)) {pred.next = node ؛ عقدة العودة. }} enq (node) ؛ عقدة العودة. }خلال هذه الفترة ، سيكون هناك العديد من المحاولات للتقدم بطلب للحصول على قفل ، وإذا كنت لا تزال غير قادر على التقديم ، فسيتم تعليقك.
Private Boolean ParkandCheckinterrupt () {locksupport.park (this) ؛ return thread.Interrupted () ؛ }وبالمثل ، إذا تم إصدار القفل ثم لم تتم مناقشة Unpark بالتفصيل هنا.
2. الحاوية المتزامنة وتحليل رمز المصدر النموذجي
2.1 concurrenthashmap
نحن نعلم أن HashMap ليس حاوية آمنة لخيط الخيط. أسهل طريقة لجعل HashMap آمن للاختبار هي الاستخدام
collections.synchronizedmap ، إنه غلاف للهاشمواب
الخريطة الثابتة العامة M = collections.synchronizedMap (new HashMap ()) ؛
وبالمثل ، بالنسبة للقائمة ، يوفر SET أيضًا طرقًا مماثلة.
ومع ذلك ، فإن هذه الطريقة مناسبة فقط للحالات التي يكون فيها مبلغ التزامن صغيرًا نسبيًا.
دعونا نلقي نظرة على تنفيذ SynchronizedMap
الخريطة النهائية الخاصة <K ، V> M ؛ // خريطة دعم الكائن النهائي mutex ؛ // الكائن الذي لمزامنة SynchronizedMap (MAP <K ، V> M) {if (m == null) رمي nullpointerxception () جديدة ؛ this.m = m ؛ mutex = هذا ؛ } SynchronizedMap (MAP <K ، V> M ، Object mutex) {this.m = m ؛ this.mutex = mutex ؛ } size int public () {synchronized (mutex) {return m.size () ؛}} public boolean isempty () {synchronized (mutex) {return m.isempty () ؛ Synchronized (mutex) {return m.containsvalue (value) ؛}} public v get (مفتاح الكائن) {synchronized (mutex) {return m.get (key) ؛}} public v put (k key ، v value) {synchronized (mutex) {return mput (key ، value) ؛ M.Remove (KEY) ؛}} public void putAll (map <؟ تمتد k ،؟ يمتد v> map) {synchronized (mutex) {m.putall (map) ؛}} public void clear () {synchronized (mutex) {m.clear () ؛}}يلف hashmap داخل ثم مزامنة كل عملية من hashmap.
نظرًا لأن كل طريقة تكتسب نفس القفل (mutex) ، فهذا يعني أن العمليات مثل PUT والإزالة حصرية بشكل متبادل ، مما يقلل إلى حد كبير من مقدار التزامن.
دعونا نرى كيف يتم تنفيذ concurrenthashmap
public v put (k key ، v value) {segment <k ، v> s ؛ if (value == null) رمي nullpointerxception () جديد ؛ int hash = hash (مفتاح) ؛ int j = (hash >>> SegmentShift) و SegmentMask ؛ if ((s = (القطعة <k ، v>) unfafe.getObject // nonvolatile ؛ إعادة فحص (شرائح ، (j << sshift) + sbase)) == null) // في insuresegment s = insuresegm (j) ؛ إرجاع S.Put (المفتاح ، التجزئة ، القيمة ، خطأ) ؛ }يوجد شريحة قطاع داخل concurrenthashmap ، والتي تقسم hashmap الكبيرة إلى عدة قطاعات (hashmap الصغيرة) ، ثم تجزئة البيانات على كل جزء. وبهذه الطريقة ، يجب أن تكون عمليات التجزئة لخيوط متعددة في قطاعات مختلفة آمنة من مؤشرات الترابط ، لذلك تحتاج فقط إلى مزامنة الخيوط في نفس الجزء ، مما يدرك فصل الأقفال ويزيد بشكل كبير من التزامن.
سيكون الأمر أكثر إزعاجًا عند استخدام concurrenthashmap.size لأنه يحتاج إلى حساب مجموع البيانات لكل قطعة. في هذا الوقت ، تحتاج إلى إضافة أقفال إلى كل قطعة ثم تقوم بإحصائيات البيانات. هذا عيب صغير بعد فصل القفل ، ولكن لا ينبغي استدعاء طريقة الحجم بتردد عالي.
فيما يتعلق بالتنفيذ ، لا نستخدم متزامنًا وقفلًا ولكن Trylock قدر الإمكان. في الوقت نفسه ، قمنا أيضًا ببعض التحسينات في تنفيذ HashMap. لن أذكرها هنا.
2.2 blockingqueue
Plockingqueue ليست حاوية عالية الأداء. لكنها حاوية جيدة جدًا لمشاركة البيانات. إنه تطبيق نموذجي للمنتجين والمستهلكين.
رسم تخطيطي:
للحصول على التفاصيل ، يمكنك التحقق من تنفيذ Java لمشاكل المنتج والمستهلك ومشاكل القارئ والكاتب.