CodeGym /مدونة جافا /Random-AR /أفضل معًا: Java وفئة Thread. الجزء السادس – أطلق النار بع...
John Squirrels
مستوى
San Francisco

أفضل معًا: Java وفئة Thread. الجزء السادس – أطلق النار بعيدًا!

نشرت في المجموعة

مقدمة

المواضيع هي شيء مثير للاهتمام. في المراجعات السابقة، نظرنا إلى بعض الأدوات المتاحة لتنفيذ تعدد العمليات. دعونا نرى ما هي الأشياء الأخرى المثيرة للاهتمام التي يمكننا القيام بها. في هذه المرحلة، نحن نعرف الكثير. على سبيل المثال، من " أفضل معًا: Java وفئة Thread. الجزء الأول - سلاسل التنفيذ "، نعلم أن فئة Thread تمثل سلاسل عمليات التنفيذ. نحن نعلم أن الخيط يؤدي بعض المهام. إذا أردنا أن تكون مهامنا قادرة على القيام بذلك run، فيجب علينا وضع علامة على الخيط بـ Runnable. أفضل معًا: Java وفئة Thread.  الجزء السادس – أطلق النار بعيدًا!  - 1للتذكر، يمكننا استخدام Tutorialspoint Online Java Compiler :
public static void main(String[] args){
	Runnable task = () -> {
 		Thread thread = Thread.currentThread();
		System.out.println("Hello from " + thread.getName());
	};
	Thread thread = new Thread(task);
	thread.start();
}
ونعلم أيضًا أن لدينا شيئًا يسمى القفل. لقد تعلمنا عن هذا في " أفضل معًا: Java وفئة الموضوع. الجزء الثاني - المزامنة . إذا حصل أحد الخيوط على قفل، فسيضطر مؤشر ترابط آخر يحاول الحصول على القفل إلى الانتظار حتى يتم تحرير القفل:
import java.util.concurrent.locks.*;

public class HelloWorld{
	public static void main(String []args){
		Lock lock = new ReentrantLock();
		Runnable task = () -> {
			lock.lock();
			Thread thread = Thread.currentThread();
			System.out.println("Hello from " + thread.getName());
			lock.unlock();
		};
		Thread thread = new Thread(task);
		thread.start();
	}
}
أعتقد أن الوقت قد حان للحديث عن الأشياء الأخرى المثيرة للاهتمام التي يمكننا القيام بها.

الإشارات

إن أبسط طريقة للتحكم في عدد سلاسل العمليات التي يمكن تشغيلها في وقت واحد هي الإشارة. إنها مثل إشارة السكك الحديدية. اللون الأخضر يعني المضي قدما. الأحمر يعني الانتظار. انتظر ماذا من الإشارة؟ وصول. للحصول على الوصول، يجب علينا الحصول عليه. وعندما لا تكون هناك حاجة للوصول، يجب أن نتخلى عنه أو نحرره. دعونا نرى كيف يعمل هذا. نحن بحاجة إلى استيراد الفصل java.util.concurrent.Semaphore. مثال:
public static void main(String[] args) throws InterruptedException {
	Semaphore semaphore = new Semaphore(0);
	Runnable task = () -> {
		try {
			semaphore.acquire();
			System.out.println("Finished");
			semaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	Thread.sleep(5000);
	semaphore.release(1);
}
كما ترون، تساعدنا هذه العمليات (الاكتساب والتحرير) على فهم كيفية عمل الإشارة. الشيء الأكثر أهمية هو أنه إذا أردنا الوصول، فيجب أن يكون للإشارة عدد موجب من التصاريح. يمكن تهيئة هذا العدد إلى رقم سالب. ويمكننا طلب (الحصول) على أكثر من تصريح واحد.

CountDownLatch

الآلية التالية هي CountDownLatch. ليس من المستغرب أن يكون هذا مزلاجًا مع العد التنازلي. نحن هنا بحاجة إلى بيان الاستيراد المناسب للفئة java.util.concurrent.CountDownLatch. إنه مثل سباق الأقدام، حيث يتجمع الجميع عند خط البداية. وبمجرد أن يصبح الجميع جاهزين، يتلقى الجميع إشارة البداية في نفس الوقت ويبدأون في وقت واحد. مثال:
public static void main(String[] args) {
	CountDownLatch countDownLatch = new CountDownLatch(3);
	Runnable task = () -> {
		try {
			countDownLatch.countDown();
			System.out.println("Countdown: " + countDownLatch.getCount());
			countDownLatch.await();
			System.out.println("Finished");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
 	}
}
أولاً، نخبر المزلاج أولاً countDown(). يُعرّف Google العد التنازلي بأنه "عملية عد الأرقام بترتيب عكسي حتى الصفر". ومن ثم نخبر المزلاج بـ await()أي انتظر حتى يصبح العداد صفراً. ومن المثير للاهتمام أن هذا عداد لمرة واحدة. تقول وثائق Java، "عندما يجب على سلاسل الرسائل العد التنازلي بشكل متكرر بهذه الطريقة، استخدم CyclicBarrier بدلاً من ذلك". بمعنى آخر، إذا كنت بحاجة إلى عداد قابل لإعادة الاستخدام، فأنت بحاجة إلى خيار مختلف: CyclicBarrier.

CyclicBarrier

كما يوحي الاسم، CyclicBarrierفهو حاجز "قابل لإعادة الاستخدام". سنحتاج إلى استيراد java.util.concurrent.CyclicBarrierالفصل. لنلقي نظرة على مثال:
public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("On your mark!");
	CyclicBarrier barrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			barrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + barrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
كما ترى، يقوم الخيط بتنفيذ awaitالطريقة، أي أنه ينتظر. وفي هذه الحالة، تنخفض قيمة الحاجز. يعتبر الحاجز مكسورًا ( barrier.isBroken()) عندما يصل العد التنازلي إلى الصفر. لإعادة ضبط الحاجز، تحتاج إلى استدعاء reset()الطريقة التي CountDownLatchلا تملكها.

مبادل

الآلية التالية هي المبادل. في هذا السياق، التبادل هو نقطة التزامن حيث يتم تبادل الأشياء المتغيرة أو تبديلها. كما هو متوقع، فإن Exchangerفئة تقوم بإجراء تبادل أو مبادلة. دعونا نلقي نظرة على أبسط مثال:
public static void main(String[] args) {
	Exchanger<String> exchanger = new Exchanger<>();
	Runnable task = () -> {
		try {
			Thread thread = Thread.currentThread();
			String withThreadName = exchanger.exchange(thread.getName());
			System.out.println(thread.getName() + " exchanged with " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
هنا نبدأ موضوعين. يقوم كل واحد منهم بتشغيل طريقة التبادل وينتظر حتى يقوم الخيط الآخر بتشغيل طريقة التبادل أيضًا. عند القيام بذلك، تقوم الخيوط بتبادل الوسائط التي تم تمريرها. مثير للاهتمام. ألا يذكرك بشيء؟ إنه يذكرنا بـ SynchronousQueueالذي يقع في قلب CachedThreadPool. وللتوضيح إليك مثال:
public static void main(String[] args) throws InterruptedException {
	SynchronousQueue<String> queue = new SynchronousQueue<>();
	Runnable task = () -> {
		try {
			System.out.println(queue.take());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	queue.put("Message");
}
يوضح المثال أنه عند بدء سلسلة محادثات جديدة، فإنها ستنتظر، لأن قائمة الانتظار ستكون فارغة. ثم يضع الخيط الرئيسي سلسلة "الرسالة" في قائمة الانتظار. والأكثر من ذلك، أنها ستتوقف أيضًا حتى يتم استلام هذه السلسلة من قائمة الانتظار. يمكنك أيضًا قراءة " SynchronousQueue vs Exchanger " للعثور على المزيد حول هذا الموضوع.

فيزر

لقد أنقذنا الأفضل للأخير — Phaser. سنحتاج إلى استيراد java.util.concurrent.Phaserالفصل. دعونا نلقي نظرة على مثال بسيط:
public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // By calling the register method, we register the current (main) thread as a party
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // We indicate that there will be a +1 party on the Phaser
        phaser.register();
        // Start a new thread
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
يوضح المثال أنه عند استخدام Phaser، ينكسر الحاجز عندما يتطابق عدد التسجيلات مع عدد الوافدين إلى الحاجز. يمكنك التعرف على المزيد Phaserمن خلال قراءة مقالة GeeksforGeeks هذه .

ملخص

كما ترون من هذه الأمثلة، هناك طرق مختلفة لمزامنة المواضيع. في وقت سابق، حاولت أن أتذكر جوانب تعدد العمليات. آمل أن تكون الأقساط السابقة في هذه السلسلة مفيدة. يقول بعض الناس أن الطريق إلى تعدد العمليات يبدأ بكتاب "Java Concurrency in Practice". على الرغم من أنه تم إصداره في عام 2006، إلا أن الناس يقولون إن الكتاب أساسي جدًا ولا يزال ذا صلة حتى يومنا هذا. على سبيل المثال، يمكنك قراءة المناقشة هنا: هل لا تزال "Java Concurrency In Practice" صالحة؟ . ومن المفيد أيضًا قراءة الروابط الموجودة في المناقشة. على سبيل المثال، يوجد رابط لكتاب The Well-Grounded Java Developer ، وسنشير بشكل خاص إلى الفصل الرابع. التزامن الحديث . هناك أيضًا مراجعة كاملة حول هذا الموضوع: هل "تزامن Java في الممارسة" لا يزال صالحًا في عصر Java 8؟ تقدم هذه المقالة أيضًا اقتراحات حول ما يجب قراءته لفهم هذا الموضوع حقًا. بعد ذلك، يمكنك إلقاء نظرة على كتاب رائع مثل OCA/OCP Java SE 8 Programmer Practice Tests . نحن مهتمون بالاختصار الثاني: OCP (Oracle Certified Professional). ستجد الاختبارات في "الفصل 20: Java Concurrency". يحتوي هذا الكتاب على أسئلة وأجوبة مع الشرح. على سبيل المثال: أفضل معًا: Java وفئة Thread.  الجزء السادس – أطلق النار بعيدًا!  - 3قد يبدأ العديد من الأشخاص بالقول إن هذا السؤال هو مثال آخر لحفظ الأساليب. من ناحية، نعم. من ناحية أخرى، يمكنك الإجابة على هذا السؤال من خلال التذكير بأن هذا ExecutorServiceنوع من "الترقية" لـ Executor. والغرض Executorمنه هو إخفاء الطريقة التي يتم بها إنشاء سلاسل الرسائل، ولكنها ليست الطريقة الرئيسية لتنفيذها، أي بدء كائن Runnableفي خيط جديد. لهذا السبب لا يوجد execute(Callable)— لأنه في ExecutorService، يتم Executorببساطة إضافة submit()الأساليب التي يمكنها إرجاع Futureكائن. بالطبع، يمكننا حفظ قائمة من الأساليب، ولكن من الأسهل بكثير أن نجعل إجابتنا مبنية على معرفتنا بطبيعة الفئات نفسها. وإليك بعض المواد الإضافية حول هذا الموضوع: أفضل معًا: Java وفئة Thread. الجزء الأول - خيوط التنفيذ أفضل معًا: Java وفئة Thread. الجزء الثاني – التزامن بشكل أفضل معًا: Java وفئة Thread. الجزء الثالث - التفاعل بشكل أفضل معًا: Java وفئة Thread. الجزء الرابع - الأشخاص القابلون للاستدعاء والمستقبل والأصدقاء أفضل معًا: Java وفئة Thread. الجزء الخامس — المنفذ، ThreadPool، الشوكة/الانضمام
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION