نعلم جميعًا أنه قبل JDK1.5، عندما كان من المقرر تنفيذ التزامن التجاري في Java، كان المبرمجون يحتاجون عادةً إلى إكمال تنفيذ التعليمات البرمجية بشكل مستقل. بالطبع، هناك بعض الأطر مفتوحة المصدر التي توفر هذه الوظائف، لكنها لا تزال غير مفيدة باعتبارها الوظائف التي تأتي مع JDK مريحة. عند تصميم برامج Java متزامنة متعددة الخيوط عالية الجودة، من أجل منع ظواهر مثل القفزات الميتة، مثل الانتظار () والإعلام () والمزامنة قبل استخدام Java، غالبًا ما يكون من الضروري مراعاة الأداء والجمود والعدالة والموارد، مثل الإدارة وكيفية تجنب الضرر الناجم عن سلامة الخيط، غالبًا ما تعتمد بعض الاستراتيجيات الأمنية الأكثر تعقيدًا، مما يزيد من عبء التطوير على المبرمجين، ولحسن الحظ، بعد ظهور JDK1.5، أصبح Sun Master (Doug Lea) أخيرًا قدمت مجموعة أدوات java.util.concurrent لتبسيط عملية الإكمال المتزامن بالنسبة لنا نحن المبرمجين الصغار الفقراء. يمكن للمطورين استخدام هذا لتقليل حالات السباق وخيوط الجمود بشكل فعال. تعمل الحزمة المتزامنة على حل هذه المشكلات بشكل جيد للغاية وتزودنا بنموذج برنامج متزامن أكثر عملية.
المنفذ: المنفذ لمهمة محددة قابلة للتشغيل.
ExecutorService: مدير تجمع الخيوط، هناك العديد من فئات التنفيذ، سأقدم بعضًا منها. يمكننا إرسال Runnable و Callable إلى المجموعة للجدولة.
الإشارة: إشارة العد
ReentrantLock: قفل حصري متبادل لإعادة الدخول، يشبه في وظيفته القفل المتزامن، ولكنه أقوى بكثير.
المستقبل: عبارة عن واجهة للتفاعل مع Runnable و Callable، مثل الحصول على النتيجة التي تم إرجاعها بعد تنفيذ مؤشر ترابط وما إلى ذلك، كما يوفر إلغاء لإنهاء مؤشر الترابط.
BlockingQueue: قائمة الانتظار المحظورة.
CompletionService: ملحق ExecutorService، والذي يمكنه الحصول على نتائج تنفيذ مؤشر الترابط
CountDownLatch: فئة مساعدة للمزامنة تسمح لخيط واحد أو أكثر بالانتظار حتى تكتمل مجموعة العمليات التي يتم تنفيذها في سلاسل رسائل أخرى.
CyclicBarrier: فئة مساعدة للمزامنة تسمح لمجموعة من سلاسل العمليات بانتظار بعضها البعض حتى يتم الوصول إلى نقطة حاجز مشتركة
المستقبل: يمثل المستقبل نتيجة الحساب غير المتزامن.
chededExecutorService: ExecutorService الذي يقوم بجدولة الأوامر ليتم تشغيلها بعد تأخير معين أو على فترات زمنية منتظمة.
وبعد ذلك، سوف نقدم لهم واحدا تلو الآخر
وصف الطريقة الرئيسية للمنفذين
newFixedThreadPool (تجمع مؤشرات الترابط ذو الحجم الثابت)
قم بإنشاء تجمع مؤشرات ترابط يمكنه إعادة استخدام مجموعة ثابتة من سلاسل الرسائل وتشغيل هذه المواضيع في قائمة انتظار مشتركة غير محدودة (فقط تلك المطلوبة ستنتظر في قائمة الانتظار للتنفيذ). إذا تم إنهاء أي مؤشر ترابط بسبب فشل أثناء التنفيذ قبل إيقاف التشغيل، فسيقوم مؤشر ترابط جديد بتنفيذ المهام اللاحقة بدلاً منه (إذا لزم الأمر).
newCachedThreadPool (تجمع سلاسل الرسائل غير المحدود، يمكنه إجراء إعادة تدوير الخيط تلقائيًا)
ينشئ تجمع سلاسل رسائل يقوم بإنشاء سلاسل رسائل جديدة حسب الحاجة، ولكنه يعيد استخدام سلاسل الرسائل التي تم إنشاؤها مسبقًا عندما تصبح متاحة. بالنسبة للبرامج التي تؤدي العديد من المهام غير المتزامنة قصيرة العمر، تعمل تجمعات مؤشرات الترابط هذه غالبًا على تحسين أداء البرنامج. سيؤدي تنفيذ الاستدعاء إلى إعادة استخدام مؤشر الترابط الذي تم إنشاؤه مسبقًا (إذا كان مؤشر الترابط متاحًا). إذا لم يتوفر أي مؤشر ترابط موجود، فسيتم إنشاء مؤشر ترابط جديد وإضافته إلى التجمع. قم بإنهاء وإزالة تلك المواضيع التي لم يتم استخدامها لمدة 60 ثانية من ذاكرة التخزين المؤقت. لذلك، لن يستخدم تجمع مؤشرات الترابط الذي يظل خاملاً لفترة طويلة أية موارد. لاحظ أنه يمكنك استخدام منشئ ThreadPoolExecutor لإنشاء تجمع مؤشرات الترابط بخصائص مشابهة ولكن بتفاصيل مختلفة (مثل معلمات المهلة).
newSingleThreadExecutor (موضوع خلفية واحد)
قم بإنشاء منفذ يستخدم مؤشر ترابط عامل واحد ويقوم بتشغيل مؤشر الترابط في قائمة انتظار غير محدودة. (لاحظ أنه إذا تم إنهاء هذا الخيط الفردي بسبب فشل أثناء التنفيذ قبل إيقاف التشغيل، فسيقوم الخيط الجديد بتنفيذ المهام اللاحقة بدلاً منه، إذا لزم الأمر). يتم ضمان تنفيذ المهام بشكل تسلسلي، ولن يكون هناك أكثر من موضوع نشط في أي وقت محدد. على عكس newFixedThreadPool(1) المكافئ، يضمن المنفذ الذي يتم إرجاعه بهذه الطريقة أنه قادر على استخدام سلاسل رسائل أخرى دون إعادة تكوينه.
تقوم هذه الأساليب بإرجاع كائنات ExecutorService، والتي يمكن فهمها على أنها تجمع مؤشرات الترابط.
وظيفة تجمع مؤشرات الترابط هذه كاملة نسبيًا. يمكنك إرسال المهام باستخدام إرسال () وإنهاء تجمع مؤشرات الترابط بإيقاف التشغيل ().
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class MyExecutor Extends Thread {private int Index;public MyExecutor(int i){ this.index=i;}public void run(){ حاول{ System.out.println("["+this.index+"] ابدأ...."); Thread.sleep((int)(Math.random()*)); System.out.println("["+this.index+"] end."); }}public static void main(String args[]){ ExecutorService Service=Executors.newFixedThreadPool(); for(int i=;i<;i++){ Service.execute(new MyExecutor(i)); //service.submit(new MyExecutor(i)); } System.out.println("إرسال النهاية";}}
على الرغم من طباعة بعض المعلومات، إلا أنه ليس من الواضح تمامًا كيف يعمل تجمع الخيوط هذا، فلنزيد وقت النوم بمقدار 10 مرات.
Thread.sleep((int)(Math.random()*10000));
إذا نظرت إلى أبعد من ذلك، سترى بوضوح أنه يمكن تنفيذ 4 سلاسل فقط. عند تنفيذ سلسلة رسائل، سيتم تنفيذ سلسلة رسائل جديدة، وهذا يعني أنه بعد إرسال جميع سلاسل الرسائل، سينتظر تجمع سلاسل الرسائل حتى يتم تنفيذ إيقاف التشغيل النهائي. سنجد أيضًا أن موضوع الإرسال موضوع في "قائمة انتظار غير محدودة". هذه قائمة انتظار مرتبة (BlockingQueue، والتي سيتم مناقشتها أدناه).
بالإضافة إلى ذلك، يستخدم الوظيفة الثابتة للمنفذين لإنشاء تجمع سلاسل رسائل ثابت، كما يوحي الاسم، لن يتم تحرير الخيط الموجود في تجمع سلاسل الرسائل، حتى لو كان خاملاً.
سيؤدي هذا إلى مشاكل في الأداء، على سبيل المثال، إذا كان حجم تجمع مؤشرات الترابط هو 200، فعند استخدام جميع سلاسل الرسائل، ستستمر جميع سلاسل الرسائل في البقاء في التجمع، وتبديل الذاكرة ومؤشر الترابط المقابل (أثناء (صحيح) + حلقة السكون. ) سوف تزيد.
إذا كنت تريد تجنب هذه المشكلة، فيجب عليك استخدام ThreadPoolExecutor() مباشرة لإنشائه. يمكنك ضبط "الحد الأقصى لعدد سلاسل الرسائل" و"الحد الأدنى لعدد سلاسل الرسائل" و"مدة استمرار تشغيل الخيط الخامل" مثل تجمع سلاسل الرسائل العام.
هذا هو الاستخدام الأساسي لتجمع الخيوط.
إشارة
إشارة العد. من الناحية النظرية، تحتفظ الإشارة بمجموعة من الأذونات. إذا لزم الأمر، يتم حظر كل اكتساب () حتى يتوفر الإذن، ثم يتم الحصول على الإذن. يضيف كل إصدار () إذنًا، مما قد يؤدي إلى تحرير مستحوذ محظور. ومع ذلك، بدلاً من استخدام كائنات الترخيص الفعلية، يقوم Semaphore ببساطة بحساب عدد التراخيص المتاحة ويتخذ الإجراء وفقًا لذلك.
غالبًا ما يتم استخدام الإشارة لتحديد عدد سلاسل الرسائل التي يمكنها الوصول إلى موارد معينة (مادية أو منطقية). على سبيل المثال، تستخدم الفئة التالية الإشارات للتحكم في الوصول إلى تجمع المحتوى:
هذا موقف حقيقي. يصطف الجميع للذهاب إلى المرحاض. لا يوجد سوى مكانين في المرحاض. عندما يأتي 10 أشخاص، عليهم الوقوف في الطابور.
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;الفئة العامة MySemaphore Extends Thread {Semaphore location;private int id;public MySemaphore(int i,Semaphore s){ this.id=i; this.position=s;} public void run(){ حاول{ if(position.availablePermits()>){ System.out.println("العميل["+this.id+"] يدخل المرحاض، هناك مساحة"); } else{ System.out.println("Customer["+ this.id+"] يدخل المرحاض، لا توجد مساحة، قائمة الانتظار"); }position.acquire(); System.out.println("العميل ["+this.id+"] يحصل على مقعد حفرة"); Thread.sleep((int)(Math.random()*)); System.out.println("انتهى العميل ["+this.id+"] من الاستخدام"); ) { e.printStackTrace(); }} public static void main(String args[]){ ExecutorService list=Executors.newCachedThreadPool(); Semaphore(); for(int i=;i<;i++){ list.submit(new MySemaphore(i+,position)); list.shutdown(); تم، بحاجة إلى التنظيف"); Position.release();}}
قفل إعادة الدخول
قفل كائن المزامنة (mutex lock) المعاد إدخاله والذي يحتوي على بعض السلوكيات والدلالات الأساسية نفسها مثل قفل الشاشة الضمني الذي يتم الوصول إليه باستخدام الأساليب والبيانات المتزامنة، ولكنه أكثر قوة.
سيكون ReentrantLock مملوكًا للخيط الذي حصل مؤخرًا على القفل بنجاح ولم يحرر القفل بعد. عندما لا يكون القفل مملوكًا لخيط آخر، فسيحصل قفل استدعاء الخيط على القفل بنجاح ويعود. إذا كان الخيط الحالي يحمل القفل بالفعل، فسيتم إرجاع هذه الطريقة على الفور. يمكنك استخدام طريقتي isHeldByCurrentThread() وgetHoldCount() للتحقق من حدوث ذلك.
يقبل منشئ هذه الفئة معلمة عدالة اختيارية.
عند التعيين على "صحيح"، في ظل التنافس من سلاسل رسائل متعددة، تميل هذه الأقفال إلى منح الوصول إلى سلسلة الرسائل التي انتظرت لفترة أطول. وإلا فإن هذا القفل لن يضمن أي أمر وصول محدد.
بالمقارنة مع الإعداد الافتراضي (استخدام القفل غير العادل)، فإن البرنامج الذي يستخدم القفل العادل سيكون له إنتاجية إجمالية منخفضة جدًا (أي سيكون بطيئًا جدًا، وغالبًا ما يكون بطيئًا للغاية) عند الوصول إليه بواسطة العديد من سلاسل العمليات، ولكن سيكون أداءه ضعيفًا في الحصول على الأقفال ومخصصات القفل المضمونة يكون الفرق صغيرًا عندما يتعلق الأمر بالتوازن.
ومع ذلك، تجدر الإشارة إلى أن القفل العادل لا يضمن عدالة جدولة سلسلة الرسائل. لذلك، قد يكون لدى أحد سلاسل العمليات العديدة التي تستخدم القفل العادل فرصًا متعددة للنجاح، وهو ما يحدث عندما لا تتم معالجة سلاسل الرسائل النشطة الأخرى ولا تحتفظ بالقفل حاليًا.
لاحظ أيضًا أن طريقة TryLock غير المحددة بالتوقيت لا تستخدم إعدادات العدالة. لأن هذه الطريقة يمكن أن تنجح طالما أن القفل متاح حتى لو كانت الخيوط الأخرى تنتظر.
يوصى بالتدرب دائمًا على الفور واستخدام كتلة المحاولة لاستدعاء القفل، في البناء قبل/بعد، يكون الرمز الأكثر شيوعًا كما يلي:
class X { قفل ReentrantLock النهائي الخاص = new ReentrantLock(); // ... public void m() { lock.lock(); فتح() } }}
المثال الخاص بي:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.ReentrantLock;تمتد الفئة العامة MyReentrantLock الموضوع {TestReentrantLock lock;معرف int خاص;MyReentrantLock العام (int i,TestReentrantLock test ){ this.id=i; this.lock=test;}تشغيل الفراغ العام(){ lock.print(id);}public static void main(String args[]){ ExecutorService Service=Executors.newCachedThreadPool(); TestReentrantLock lock=new TestReentrantLock(); for(int i=;i<;i++){service. Submit(new MyReentrantLock(i,lock)); } Service.shutdown();}}class TestReentrantLock{private ReentrantLock lock=new ReentrantLock(); public void print(int str){try{ lock.lock(); )); }catch(Exception e){ e.printStackTrace(); } أخيرا{ System.out.println(str+"release");
BlockingQueue
قائمة انتظار تدعم عمليتين إضافيتين: الانتظار حتى تصبح قائمة الانتظار غير فارغة عند استرداد عنصر، وانتظار توفر المساحة عند تخزين عنصر.
لا يقبل BlockingQueue العناصر الفارغة. تقوم بعض التطبيقات بطرح NullPointerException عند محاولة إضافة عنصر فارغ أو وضعه أو تقديمه. يتم استخدام null كقيمة تحذير للإشارة إلى فشل عملية الاستقصاء.
يمكن أن تكون BlockingQueue محدودة السعة. يمكن أن يكون له سعة متبقية في أي وقت، ولا يمكنه بعدها وضع عناصر إضافية دون حظر.
تقوم قائمة BlockingQueue التي لا تحتوي على أي قيود على السعة الداخلية دائمًا بالإبلاغ عن Integer.MAX_VALUE للسعة المتبقية.
يتم استخدام تطبيق BlockingQueue بشكل أساسي لقوائم انتظار المنتجين والمستهلكين، ولكنه يدعم بالإضافة إلى ذلك واجهة المجموعة. لذلك، على سبيل المثال، من الممكن إزالة عنصر عشوائي من قائمة الانتظار باستخدام إزالة (x).
ومع ذلك، لا يتم تنفيذ هذه العملية بكفاءة عادةً، ولا يمكن استخدامها إلا من حين لآخر وبطريقة مخططة، كما هو الحال عند فصل رسالة.
يعد تطبيق BlockingQueue آمنًا لمؤشر الترابط. يمكن لجميع أساليب الانتظار استخدام القفل الداخلي أو أشكال أخرى من التحكم في التزامن لتحقيق أغراضها تلقائيًا.
ومع ذلك، لا يتم بالضرورة تنفيذ عدد كبير من عمليات التجميع (addAll، وcontainsAll، وretainAll، وremoveAll) تلقائيًا ما لم يُنص على ذلك على وجه التحديد في التنفيذ.
لذلك، على سبيل المثال، قد يفشل addAll(c) (طرح استثناء) بعد إضافة بعض العناصر فقط في c.
لا يدعم BlockingQueue بشكل أساسي أي نوع من عمليات "الإغلاق" أو "إيقاف التشغيل" للإشارة إلى أنه لن تتم إضافة المزيد من العناصر.
تعتمد الحاجة إلى هذه الوظيفة واستخدامها على التنفيذ. على سبيل المثال، تتمثل الإستراتيجية الشائعة في إدراج كائنات خاصة في نهاية التدفق أو كائنات سامة في المنتج وتفسيرها بناءً على وقت حصول المستهلك عليها.
يوضح المثال التالي الوظيفة الأساسية لقائمة الانتظار المحظورة هذه.
import java.util.concurrent.BlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.LinkedBlockingQueue;الفئة العامة MyBlockingQueue تمتد الموضوع {public static BlockingQueue<String> queue = LinkedBlockingQueue الجديد<String>();مؤشر int الخاص;public MyBlockingQueue(int i) { this.index = i;} public void run() { حاول { queue.put(String.valueOf(this.index)); System.out.println("{" + this.index + " } في قائمة الانتظار!"); } Catch (Exception e) { e.printStackTrace(); }} public static void main(String args[]) { ExecutorService Service = Executors.newCachedThreadPool(); for (int i = ; i < ; i++) {service.submit(new MyBlockingQueue(i)); Thread thread = new Thread() { public void run() { حاول { while (true) { Thread.sleep((int) (Math.random() * )); if(MyBlockingQueue.queue.isEmpty()) Break; MyBlockingQueue.queue.take(); System.out.println(str + " has take!"); اغلق()؛}}
--------------------------نتائج التنفيذ-----------------
{0} في قائمة الانتظار!
{1} في قائمة الانتظار!
{2} في قائمة الانتظار!
{3} في قائمة الانتظار!
0 اتخذت!
{4} في قائمة الانتظار!
لقد اتخذت 1!
{6} في قائمة الانتظار!
وقد اتخذت 2!
{7} في قائمة الانتظار!
وقد اتخذت 3!
{8} في قائمة الانتظار!
وقد اتخذت 4!
{5} في قائمة الانتظار!
وقد اتخذت 6!
{9} في قائمة الانتظار!
وقد اتخذت 7!
وقد اتخذت 8!
وقد اتخذت 5!
وقد اتخذت 9!
----------------------------------------
خدمة الإكمال
خدمة تفصل بين إنتاج مهام جديدة غير متزامنة واستهلاك نتائج المهام المكتملة. يقدم المنتج المهمة المطلوب تنفيذها. يأخذ المستخدم المهام المكتملة ويعالج نتائجها بالترتيب الذي تم إكمالها به. على سبيل المثال، يمكن استخدام خدمة CompletionService لإدارة عمليات الإدخال والإخراج غير المتزامنة. يتم إرسال مهمة تنفيذ عملية القراءة كجزء من البرنامج أو النظام. ثم، عند اكتمال عملية القراءة، يتم تنفيذ عمليات أخرى في جزء مختلف من البرنامج ربما يكون الترتيب الذي تم به طلب العمليات مختلفًا.
عادةً، تعتمد خدمة CompletionService على منفذ منفصل لتنفيذ المهمة فعليًا، وفي هذه الحالة تقوم خدمة CompletionService بإدارة قائمة انتظار الإكمال الداخلية فقط. توفر فئة ExecutorCompletionService تطبيقًا لهذه الطريقة.
import java.util.concurrent.Callable;import java.util.concurrent.CompletionService;import java.util.concurrent.ExecutorCompletionService;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;الفئة العامة MyCompletionService تنفذ Callable <String> {معرف int الخاص؛ MyCompletionService العام (int i) { this.id=i;}public static void main(String[] args) throws Exception{ ExecutorService Service=Executors.newCachedThreadPool(); CompletionService<String>Complete=new ExecutorCompletionService<String>(service); i<;i++){Complete.submit(new MyCompletionService(i)); } for(int i=;i<;i++){ System.out.println(completion.take().get()); } Service.shutdown();} public String call() throws Exception { Integer time=(int)(Math.random()*); System.out.println(this.id+"start"); Thread.sleep(time); System.out.println(this.id+"end"); e.printStackTrace(); } return this.id+":"+time;}}
CountDownLatch
فئة مساعدة للمزامنة تسمح لخيط واحد أو أكثر بالانتظار حتى تكتمل مجموعة العمليات التي يتم تنفيذها في سلاسل رسائل أخرى.
تهيئة CountDownLatch بالعدد المحدد. بسبب استدعاء طريقة countDown()، يتم حظر طريقة الانتظار حتى يصل العدد الحالي إلى الصفر.
بعد ذلك، يتم تحرير جميع سلاسل الرسائل المنتظرة وتعود جميع الاستدعاءات اللاحقة على الفور. يحدث هذا السلوك مرة واحدة فقط - لا يمكن إعادة تعيين العدد. إذا كنت بحاجة إلى إعادة ضبط العد، فكر في استخدام CyclicBarrier.
CountDownLatch هي أداة مزامنة عامة لها العديد من الاستخدامات. استخدم CountDownLatch الذي تمت تهيئته بالعد 1 كمزلاج تشغيل/إيقاف بسيط، أو إدخال: جميع سلاسل الرسائل التي يتم الاتصال بها تنتظر الانتظار عند الإدخال حتى يتم فتح الإدخال بواسطة مؤشر الترابط الذي يستدعي countDown().
يمكن أن يتسبب CountDownLatch الذي تمت تهيئته بـ N في انتظار مؤشر الترابط حتى اكتمال N من العمليات، أو الانتظار حتى تكتمل العملية N مرات.
من الميزات المفيدة لـ CountDownLatch أنها لا تتطلب من الخيط الذي يستدعي طريقة countDown الانتظار حتى يصل العدد إلى الصفر قبل المتابعة، بل يمنع أي خيط من الاستمرار خلال الانتظار حتى تتمكن جميع سلاسل الرسائل من المرور.
المثال أدناه كتبه شخص آخر وهو واضح جدًا.
import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestCountDownLatch {public static void main(String[] args) throws InterruptedException { // بدء قفل العد التنازلي النهائي CountDownLatch begin = new CountDownLatch(); // نهاية قفل العد التنازلي Final CountDownLatch end = new CountDownLatch(); { حاول { begin.await();// منع Thread.sleep((long) (Math.random() * )); System.out.println("No." + NO + "arrived"); println("بدء اللعبة"); begin.countDown(); end.await();
أهم طرق CountDownLatch هي countDown() و Waiting(). الأولى تقوم بالعد التنازلي بشكل أساسي مرة واحدة، والأخيرة تنتظر العد التنازلي إلى 0. إذا لم تصل إلى 0، فسوف تقوم فقط بالحظر والانتظار.
CyclicBarrier
فئة مساعدة للمزامنة تسمح لمجموعة من سلاسل العمليات بانتظار بعضها البعض حتى يتم الوصول إلى نقطة حاجز مشتركة.
يعد CyclicBarrier مفيدًا في البرامج التي تتضمن مجموعة من سلاسل العمليات ذات الحجم الثابت والتي يجب أن تنتظر بعضها البعض من وقت لآخر. نظرًا لأنه يمكن إعادة استخدام الحاجز بعد تحرير خيط الانتظار، فإنه يُسمى حاجزًا دوريًا.
يدعم CyclicBarrier أمر Runnable الاختياري الذي يتم تشغيله مرة واحدة فقط عند كل نقطة حاجز، بعد وصول آخر خيط في مجموعة سلاسل الرسائل (ولكن قبل تحرير جميع سلاسل الرسائل). تعد عملية الحاجز هذه مفيدة عند تحديث الحالة المشتركة قبل متابعة جميع سلاسل الرسائل المشاركة.
مثال للاستخدام: ما يلي هو مثال لاستخدام الحاجز في تصميم التحلل المتوازي، وهو مثال كلاسيكي للغاية لمجموعة سياحية:
import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; public class TestCyclicBarrier { // الوقت اللازم للمشي لمسافات طويلة: شنتشن، قوانغتشو، شاوقوان، تشانغشا، ووهان Private static int[] timeWalk = { , , , , , }; // جولة ذاتية القيادة Private static int[] timeSelf = { , , , , }; , , } ; static String now() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); sdf.format(new Date()) + ": "; static class Tour Implements Runnable { public int[] times; public String TourName; this.times = times; this.tourName = TourName; this.barrier = Barrier; } public void run() {try { Thread.sleep(times[] * ); System.out.println(now() + TourName + "تم الوصول إلى Shenzhen"); Thread.sleep(times[] * ); ); Thread.await(); System.out.println(now() + "تم الوصول إلى تشانغشا"); System.out.println(now() + TourName + "تم الوصول إلى ووهان"); (BrokenBarrierException e) { } } } public static void main(String[] args) { // ثلاث مجموعات سياحية حاجز CyclicBarrier = new CyclicBarrier(); "WalkTour"، timeWalk)); exec.submit(new Tour(barrier, "SelfTour"، timeSelf));// عندما نعلق الكود التالي، سنجد أن البرنامج محظور ولا يمكنه الاستمرار في التشغيل. exec.submit(new Tour(barrier, "BusTour", timeBus)); exec.shutdown(); }}
أهم سمة لـ CyclicBarrier هي عدد المشاركين، وأهم طريقة هي الانتظار (). عندما تستدعي جميع سلاسل الرسائل الانتظار () ، فهذا يعني أن هذه الخيوط يمكن أن تستمر في التنفيذ، وإلا فسوف تنتظر.
مستقبل
يمثل المستقبل نتيجة الحساب غير المتزامن. ويوفر طرقًا للتحقق من اكتمال العملية الحسابية، وانتظار اكتمال العملية الحسابية، واسترداد نتيجة العملية الحسابية.
بعد اكتمال الحساب، يمكن استخدام طريقة get فقط لاسترداد النتائج، إذا لزم الأمر، يمكن حظر هذه الطريقة قبل اكتمال الحساب. يتم الإلغاء عن طريق طريقة الإلغاء.
يتم توفير طرق إضافية لتحديد ما إذا كانت المهمة قد اكتملت بشكل طبيعي أو تم إلغاؤها. بمجرد اكتمال الحساب، لا يمكن إلغاؤه.
إذا كنت تستخدم Future لقابلية الإلغاء ولكنك لا تقدم نتيجة قابلة للاستخدام، فيمكنك إعلان نوع رسمي Future<?> وإرجاع قيمة فارغة كنتيجة للمهمة الأساسية.
لقد رأينا هذا في CompletionService سابقًا، وظيفة هذا المستقبل، ويمكن تحديدها ككائن إرجاع عند إرسال مؤشر الترابط.
خدمة تنفيذية مجدولة
ExecutorService الذي يقوم بجدولة الأوامر ليتم تشغيلها بعد تأخير معين أو على فترات زمنية منتظمة.
تقوم طريقة الجدولة بإنشاء مهام ذات تأخيرات مختلفة وإرجاع كائن مهمة يمكن استخدامه لإلغاء التنفيذ أو التحقق منه. تعمل طريقتا ScholtoAtFixedRate وScheduleWithFixedDelay على إنشاء وتنفيذ مهام معينة يتم تشغيلها بشكل دوري حتى يتم إلغاؤها.
تتم جدولة الأوامر المرسلة باستخدام Executor.execute(java.lang.Runnable) وطريقة الإرسال الخاصة بـ ExecutorService مع التأخير المطلوب وهو 0.
يُسمح بالتأخيرات الصفرية والسلبية (ولكن ليس الفترات) في طريقة الجدولة، ويتم التعامل معها على أنها طلبات يتم تنفيذها على الفور.
تقبل جميع أساليب الجدولة التأخيرات والفترات النسبية كمعلمات، بدلاً من الأوقات أو التواريخ المطلقة. من السهل تحويل الوقت المطلق الذي يمثله التاريخ إلى النموذج المطلوب.
على سبيل المثال، لجدولة التشغيل في تاريخ لاحق، استخدم: جدول (مهمة، تاريخ.getTime() - System.currentTimeMillis()، TimeUnit.MILLISECONDS).
ومع ذلك، لاحظ أنه بسبب بروتوكولات مزامنة وقت الشبكة، أو انحراف الساعة، أو عوامل أخرى، ليس من الضروري أن يتطابق تاريخ انتهاء الصلاحية المتأخر نسبيًا مع التاريخ الحالي للمهمة الممكّنة.
توفر فئة Executors أساليب مصنع ملائمة لتطبيق ScheddedExecutorService المتوفر في هذه الحزمة.
الأمثلة التالية شائعة أيضًا على الإنترنت.
import static java.util.concurrent.TimeUnit.SECONDS;import java.util.Date;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledFuture;فئة عامة TestScheduledThread { public static void main(String[] args) { جدولة خدمة جدولة ExecutorService النهائية = Executors.newScheduledThreadPool(); Final Runnable beeper = new Runnable() { int count = ; public void run() { System.out.println(new Date() + " beep " + (++count) }); // تشغيل بعد ثواني وكل ثانية FinalchededFuture beeperHandle =scheduler.scheduleAtFixedRate(beeper, , , SECONDS); // قم بالتشغيل بعد ثوانٍ، وانتظر لمدة ثوانٍ بعد انتهاء تشغيل المهمة الأخيرة، ثم أعد تشغيلها في كل مرة Final SchededFuture beeperHandle =scheduler.scheduleWithFixedDelay(beeper, , , SECONDS); // قم بإنهاء المهمة بعد ثوانٍ وأغلق جدولة الجدولة .schedule(new Runnable() { public void run() { beeperHandle.cancel(true); beeperHandle.cancel(true); Schutdown(); } }, SECONDS);}}
بهذه الطريقة، قمنا بتلخيص الوظائف الأكثر أهمية للحزمة المتزامنة، ونأمل أن تكون مفيدة لفهمنا.