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

أفضل معًا: Java وفئة Thread. الجزء الثاني – التزامن

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

مقدمة

لذلك، نحن نعلم أن جافا لديها المواضيع. يمكنك أن تقرأ عن ذلك في المراجعة التي تحمل عنوان Better Together: Java and the Thread class. الجزء الأول – خيوط التنفيذ . المواضيع ضرورية لأداء العمل بالتوازي. وهذا يجعل من المحتمل جدًا أن تتفاعل الخيوط بطريقة أو بأخرى مع بعضها البعض. دعونا نلقي نظرة على كيفية حدوث ذلك وما هي الأدوات الأساسية التي لدينا. أفضل معًا: Java وفئة Thread.  الجزء الثاني — التزامن - 1

أَثْمَر

Thread.yield() محير ونادرًا ما يستخدم. يتم وصفه بعدة طرق مختلفة على الإنترنت. بما في ذلك بعض الأشخاص الذين يكتبون أن هناك قائمة انتظار من سلاسل الرسائل، حيث سيتم نزول الخيط بناءً على أولويات الخيط. يكتب أشخاص آخرون أن الخيط سيغير حالته من "قيد التشغيل" إلى "قابل للتشغيل" (على الرغم من عدم وجود تمييز بين هذه الحالات، أي أن Java لا تميز بينهما). والحقيقة هي أن الأمر كله أقل شهرة ولكنه أبسط إلى حد ما. أفضل معًا: Java وفئة Thread.  الجزء الثاني - التزامن - 2يوجد خطأ ( JDK-6416721: (سلسلة المواصفات) Fix Thread.yield() javadoc ) تم تسجيله في yield()وثائق الطريقة. إذا قرأته، فمن الواضح أن هذه yield()الطريقة تقدم في الواقع بعض التوصيات فقط إلى برنامج جدولة سلاسل رسائل Java، والتي يمكن من خلالها منح هذا الخيط وقتًا أقل للتنفيذ. ولكن ما يحدث بالفعل، أي ما إذا كان المجدول يتصرف بناءً على التوصية وما يفعله بشكل عام، يعتمد على تنفيذ JVM ونظام التشغيل. وقد يعتمد الأمر على بعض العوامل الأخرى أيضًا. يرجع كل الالتباس على الأرجح إلى حقيقة أنه تمت إعادة التفكير في تعدد مؤشرات الترابط مع تطور لغة Java. اقرأ المزيد في النظرة العامة هنا: مقدمة موجزة عن Java Thread.yield() .

ينام

يمكن أن ينتقل الخيط إلى وضع السكون أثناء تنفيذه. هذا هو أسهل نوع من التفاعل مع المواضيع الأخرى. نظام التشغيل الذي يقوم بتشغيل جهاز Java الظاهري الذي يقوم بتشغيل كود Java الخاص بنا لديه برنامج جدولة سلاسل رسائل خاص به . فهو يقرر أي موضوع سيبدأ ومتى. لا يمكن للمبرمج التفاعل مع هذا المجدول مباشرة من كود Java، فقط من خلال JVM. يمكنه أن يطلب من المجدول إيقاف الخيط مؤقتًا لفترة من الوقت، أي وضعه في وضع السكون. يمكنك قراءة المزيد في هذه المقالات: Thread.sleep() و كيف يعمل Multithreading . يمكنك أيضًا التحقق من كيفية عمل سلاسل الرسائل في أنظمة تشغيل Windows: Internals of Windows Thread . والآن دعونا نرى ذلك بأعيننا. احفظ الكود التالي في ملف اسمه HelloWorldApp.java:
class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
كما ترون، لدينا بعض المهام التي تنتظر لمدة 60 ثانية، وبعد ذلك ينتهي البرنامج. نقوم بالتجميع باستخدام الأمر " javac HelloWorldApp.java" ثم نقوم بتشغيل البرنامج باستخدام " java HelloWorldApp". من الأفضل أن تبدأ البرنامج في نافذة منفصلة. على سبيل المثال، في نظام التشغيل Windows، يكون الأمر كما يلي: start java HelloWorldApp. نستخدم الأمر jps للحصول على PID (معرف العملية)، ونفتح قائمة سلاسل الرسائل باستخدام " jvisualvm --openpid pid: أفضل معًا: Java وفئة Thread.  الجزء الثاني - التزامن - 3كما ترون، أصبح مؤشر الترابط الخاص بنا الآن في حالة "السكون". في الواقع، هناك طريقة أكثر أناقة للمساعدة موضوعنا لديه أحلام سعيدة:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
هل لاحظت أننا نتعامل InterruptedExceptionفي كل مكان؟ دعونا نفهم لماذا.

الموضوع. المقاطعة ()

المشكلة هي أنه أثناء انتظار/سكون الخيط، قد يرغب شخص ما في مقاطعته. في هذه الحالة، نحن نتعامل مع InterruptedException. تم إنشاء هذه الآلية بعد Thread.stop()إعلان أن الطريقة مهملة، أي أنها قديمة وغير مرغوب فيها. كان السبب هو أنه عندما stop()تم استدعاء الطريقة، تم "قتل" الخيط ببساطة، وهو أمر لا يمكن التنبؤ به على الإطلاق. لم نتمكن من معرفة متى سيتم إيقاف مؤشر الترابط، ولم نتمكن من ضمان اتساق البيانات. تخيل أنك تكتب البيانات إلى ملف أثناء إيقاف مؤشر الترابط. بدلاً من قتل سلسلة المحادثات، قرر منشئو Java أنه سيكون من المنطقي أكثر إخبارها بوجوب مقاطعتها. إن كيفية الرد على هذه المعلومات هي مسألة يقررها الموضوع نفسه. لمزيد من التفاصيل، اقرأ لماذا تم إهمال Thread.stop؟ على موقع أوراكل. لنلقي نظرة على مثال:
public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
في هذا المثال، لن ننتظر 60 ثانية. بدلاً من ذلك، سنعرض على الفور "تمت المقاطعة". وذلك لأننا قمنا بتسمية interrupt()الطريقة الموجودة على الخيط. تقوم هذه الطريقة بتعيين علامة داخلية تسمى "حالة المقاطعة". أي أن كل مؤشر ترابط له علامة داخلية لا يمكن الوصول إليها مباشرة. لكن لدينا طرقًا أصلية للتفاعل مع هذا العلم. لكن هذه ليست الطريقة الوحيدة. قد يكون الخيط قيد التشغيل، ولا ينتظر شيئًا ما، بل يقوم ببساطة بتنفيذ الإجراءات. ولكنها قد تتوقع أن الآخرين سوف يرغبون في إنهاء عملها في وقت محدد. على سبيل المثال:
public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
في المثال أعلاه، whileسيتم تنفيذ الحلقة حتى تتم مقاطعة الخيط خارجيًا. أما بالنسبة للعلم isInterrupted، فمن المهم أن نعرف أنه إذا التقطنا علامة InterruptedException، فسيتم إعادة تعيين علامة isInterrupted، ثم isInterrupted()ستعود خطأ. تحتوي فئة Thread أيضًا على طريقة Thread.interrupted() ثابتة تنطبق فقط على مؤشر الترابط الحالي، ولكن هذه الطريقة تعيد تعيين العلامة إلى خطأ! اقرأ المزيد في هذا الفصل بعنوان مقاطعة الموضوع .

انضم (انتظر حتى ينتهي موضوع آخر)

أبسط نوع من الانتظار هو انتظار انتهاء موضوع آخر.
public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
في هذا المثال، سوف ينام الخيط الجديد لمدة 5 ثوانٍ. في الوقت نفسه، سينتظر الخيط الرئيسي حتى يستيقظ الخيط النائم وينتهي من عمله. إذا نظرت إلى حالة سلسلة الرسائل في JVisualVM، فستبدو كما يلي: أفضل معًا: Java وفئة Thread.  الجزء الثاني - التزامن - 4بفضل أدوات المراقبة، يمكنك رؤية ما يحدث مع سلسلة الرسائل. الطريقة joinبسيطة جدًا، لأنها مجرد طريقة تحتوي على كود Java ويتم تنفيذها wait()طالما أن الخيط الذي يتم الاتصال به على قيد الحياة. بمجرد أن يموت الخيط (عند الانتهاء من عمله)، ينقطع الانتظار. وهذا هو كل سحر الطريقة join(). لذلك، دعونا ننتقل إلى الشيء الأكثر إثارة للاهتمام.

شاشة

يتضمن تعدد مؤشرات الترابط مفهوم الشاشة. تأتي كلمة "مراقب" إلى اللغة الإنجليزية عن طريق اللغة اللاتينية في القرن السادس عشر وتعني "أداة أو جهاز يستخدم للمراقبة أو التحقق أو الاحتفاظ بسجل مستمر لعملية ما". وفي سياق هذه المقالة، سنحاول تغطية الأساسيات. لمن يريد التفاصيل، يرجى التعمق في المواد المرتبطة. نبدأ رحلتنا مع مواصفات لغة جافا (JLS): 17.1. التزامن . تقول ما يلي: أفضل معًا: Java وفئة Thread.  الجزء الثاني — التزامن - 5اتضح أن Java تستخدم آلية "مراقبة" للمزامنة بين سلاسل الرسائل. ترتبط الشاشة بكل كائن، ويمكن للخيوط الحصول عليها lock()أو تحريرها باستخدام unlock(). بعد ذلك، سنجد البرنامج التعليمي على موقع Oracle الإلكتروني: Intrinsic Locks and Synchronization . يقول هذا البرنامج التعليمي أن مزامنة Java مبنية على كيان داخلي يسمى القفل الداخلي أو قفل الشاشة . غالبًا ما يُطلق على هذا القفل اسم " الشاشة ". نرى أيضًا مرة أخرى أن كل كائن في Java له قفل داخلي مرتبط به. يمكنك قراءة Java - الأقفال والمزامنة الجوهرية . بعد ذلك، سيكون من المهم فهم كيفية ربط كائن في Java بشاشة. في Java، يحتوي كل كائن على رأس يخزن بيانات التعريف الداخلية غير المتاحة للمبرمج من التعليمات البرمجية، ولكن يحتاجها الجهاز الظاهري للعمل بشكل صحيح مع الكائنات. يتضمن رأس الكائن "كلمة علامة"، والتي تبدو كما يلي: أفضل معًا: Java وفئة Thread.  الجزء الثاني — التزامن - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

إليك مقالة JavaWorld المفيدة جدًا: كيف يقوم جهاز Java الظاهري بإجراء مزامنة سلسلة الرسائل . يجب دمج هذه المقالة مع الوصف من قسم "الملخص" للمشكلة التالية في نظام تتبع أخطاء JDK: JDK-8183909 . يمكنك قراءة نفس الشيء هنا: JEP-8183909 . لذلك، في Java، ترتبط الشاشة بكائن وتستخدم لحظر الخيط عندما يحاول الخيط الحصول على (أو الحصول على) القفل. إليك أبسط مثال:
public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
هنا، يستخدم مؤشر الترابط الحالي (الذي يتم تنفيذ أسطر التعليمات البرمجية عليه) الكلمة synchronizedالأساسية لمحاولة استخدام الشاشة المرتبطة بالمتغير object"\للحصول على/الحصول على القفل. إذا لم يتنافس أي شخص آخر على الشاشة (أي لا يقوم أي شخص آخر بتشغيل تعليمات برمجية متزامنة باستخدام نفس الكائن)، فقد تحاول Java إجراء تحسين يسمى "القفل المتحيز". تتم إضافة علامة ذات صلة وسجل حول مؤشر الترابط الذي يمتلك قفل الشاشة إلى كلمة العلامة في رأس الكائن. وهذا يقلل من الحمل المطلوب لقفل الشاشة. إذا كانت الشاشة مملوكة سابقًا لخيط آخر، فإن هذا القفل لا يكفي. يتحول JVM إلى النوع التالي من القفل: "القفل الأساسي". ويستخدم عمليات المقارنة والمبادلة (CAS). علاوة على ذلك، لم تعد كلمة علامة رأس الكائن نفسها تخزن كلمة العلامة، بل هي إشارة إلى مكان تخزينها، وتتغير العلامة حتى يفهم JVM أننا نستخدم القفل الأساسي. إذا تنافست عدة سلاسل (تتنافس) على شاشة (أحدها حصل على القفل، والثاني ينتظر تحرير القفل)، فستتغير العلامة الموجودة في كلمة العلامة، وتقوم كلمة العلامة الآن بتخزين مرجع إلى الشاشة ككائن - بعض الكيانات الداخلية لـ JVM. كما هو مذكور في اقتراح تحسين JDK (JEP)، يتطلب هذا الموقف مساحة في منطقة الكومة الأصلية من الذاكرة لتخزين هذا الكيان. سيتم تخزين المرجع إلى موقع ذاكرة هذا الكيان الداخلي في كلمة علامة رأس الكائن. وبالتالي، فإن الشاشة هي في الحقيقة آلية لمزامنة الوصول إلى الموارد المشتركة بين عدة سلاسل عمليات. يقوم JVM بالتبديل بين العديد من تطبيقات هذه الآلية. لذلك، للتبسيط، عندما نتحدث عن الشاشة، فإننا نتحدث في الواقع عن الأقفال. أفضل معًا: Java وفئة Thread.  الجزء الثاني — التزامن - 7

متزامن (في انتظار القفل)

كما رأينا سابقًا، يرتبط مفهوم "الكتلة المتزامنة" (أو "القسم الحرج") ارتباطًا وثيقًا بمفهوم الشاشة. ألق نظرة على مثال:
public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
هنا، يقوم الخيط الرئيسي أولاً بتمرير كائن المهمة إلى الخيط الجديد، ثم يحصل على القفل على الفور وينفذ عملية طويلة معه (8 ثوانٍ). كل هذا الوقت، المهمة غير قادرة على المتابعة، لأنها لا تستطيع الدخول إلى synchronizedالكتلة، لأن القفل قد تم الحصول عليه بالفعل. إذا لم يتمكن الخيط من الحصول على القفل، فسوف ينتظر الشاشة. بمجرد حصوله على القفل، سيستمر التنفيذ. عندما يخرج الخيط من الشاشة، فإنه يحرر القفل. في JVisualVM، يبدو الأمر كما يلي: أفضل معًا: Java وفئة Thread.  الجزء الثاني - التزامن - 8كما ترى في JVisualVM، الحالة هي "Monitor"، مما يعني أن الخيط محظور ولا يمكنه التقاط الشاشة. يمكنك أيضًا استخدام التعليمات البرمجية لتحديد حالة سلسلة الرسائل، لكن أسماء الحالة المحددة بهذه الطريقة لا تتطابق مع الأسماء المستخدمة في JVisualVM، على الرغم من تشابهها. في هذه الحالة، th1.getState()ستعود العبارة الموجودة في حلقة for إلى BLOCKED ، لأنه طالما أن الحلقة قيد التشغيل، فإن lockشاشة الكائن مشغولة بالخيط main، th1ويتم حظر الخيط ولا يمكن المتابعة حتى يتم تحرير القفل. بالإضافة إلى الكتل المتزامنة، يمكن مزامنة الطريقة بأكملها. على سبيل المثال، إليك طريقة من الفصل HashTable:
public synchronized int size() {
	return count;
}
سيتم تنفيذ هذه الطريقة بواسطة مؤشر ترابط واحد فقط في أي وقت محدد. هل نحن حقا بحاجة إلى القفل؟ نعم، نحن في حاجة إليها. في حالة أساليب المثيل، يعمل الكائن "هذا" (الكائن الحالي) كقفل. هناك مناقشة مثيرة للاهتمام حول هذا الموضوع هنا: هل هناك ميزة لاستخدام الطريقة المتزامنة بدلاً من الكتلة المتزامنة؟ . إذا كانت الطريقة ثابتة، فلن يكون القفل هو الكائن "هذا" (نظرًا لعدم وجود كائن "هذا" للطريقة الثابتة)، بل كائن فئة (على سبيل المثال، ) Integer.class.

انتظر (في انتظار الشاشة). طرق الإخطار () وإخطار الكل ().

تحتوي فئة Thread على طريقة انتظار أخرى مرتبطة بالشاشة. على عكس sleep()و join()، لا يمكن استدعاء هذه الطريقة ببساطة. اسمها wait(). يتم استدعاء هذه waitالطريقة على الكائن المرتبط بالشاشة الذي نريد انتظاره. دعونا نرى مثالا:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
في JVisualVM، يبدو الأمر كما يلي: أفضل معًا: Java وفئة Thread.  الجزء الثاني – التزامن – 10لفهم كيفية عمل ذلك، تذكر أن الأساليب wait()والطرق notify()مرتبطة بـ java.lang.Object. قد يبدو من الغريب وجود طرق مرتبطة بالخيط في Objectالفصل. لكن السبب وراء ذلك يتكشف الآن. ستتذكر أن كل كائن في Java له رأس. يحتوي الرأس على معلومات متنوعة عن التدبير المنزلي، بما في ذلك معلومات حول الشاشة، أي حالة القفل. تذكر أن كل كائن، أو مثيل لفئة، يرتبط بكيان داخلي في JVM، يسمى القفل أو الشاشة الجوهرية. في المثال أعلاه، يشير رمز كائن المهمة إلى أننا نقوم بإدخال الكتلة المتزامنة للشاشة المرتبطة بالكائن lock. إذا نجحنا في الحصول على القفل لهذه الشاشة، فسيتم wait()استدعاؤه. سيقوم الخيط الذي ينفذ المهمة بتحرير lockشاشة الكائن، ولكنه سيدخل في قائمة انتظار سلاسل الرسائل التي تنتظر الإشعار من lockشاشة الكائن. تسمى قائمة الانتظار هذه بـ WAIT SET، وهو ما يعكس الغرض منها بشكل أكثر دقة. أي أنها مجموعة أكثر من كونها قائمة انتظار. يقوم مؤشر الترابط mainبإنشاء مؤشر ترابط جديد مع كائن المهمة، ويبدأه، وينتظر 3 ثوانٍ. وهذا يجعل من المحتمل جدًا أن يتمكن الخيط الجديد من الحصول على القفل قبل الخيط main، والدخول في قائمة انتظار الشاشة. بعد ذلك، mainيدخل الخيط نفسه إلى lockالكتلة المتزامنة للكائن ويقوم بإجراء إشعار الخيط باستخدام الشاشة. بعد إرسال الإعلام، mainيقوم الخيط بتحرير lockشاشة الكائن، ويستمر الخيط الجديد، الذي كان ينتظر سابقًا lockتحرير شاشة الكائن، في التنفيذ. من الممكن إرسال إشعار إلى مؤشر ترابط واحد فقط ( notify()) أو في وقت واحد إلى كافة سلاسل الرسائل في قائمة الانتظار ( notifyAll()). اقرأ المزيد هنا: الفرق بين notify() و notifyAll() في Java . من المهم ملاحظة أن ترتيب الإشعارات يعتمد على كيفية تنفيذ JVM. اقرأ المزيد هنا: كيفية حل مشكلة المجاعة باستخدام الإشعارات والإخطارات؟ . يمكن إجراء المزامنة دون تحديد كائن. يمكنك القيام بذلك عندما تتم مزامنة الطريقة بأكملها بدلاً من كتلة واحدة من التعليمات البرمجية. على سبيل المثال، بالنسبة للطرق الثابتة، سيكون القفل كائن فئة (يتم الحصول عليه عبر .class):
public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
فيما يتعلق باستخدام الأقفال، كلا الطريقتين متماثلتان. إذا لم تكن الطريقة ثابتة، فسيتم إجراء المزامنة باستخدام التيار instance، أي باستخدام this. بالمناسبة، قلنا سابقًا أنه يمكنك استخدام الطريقة getState()للحصول على حالة الموضوع. على سبيل المثال، بالنسبة لمؤشر الترابط الموجود في قائمة الانتظار في انتظار جهاز العرض، ستكون الحالة WAITING أو TIMED_WAITING، إذا wait()حددت الطريقة مهلة. أفضل معًا: Java وفئة Thread.  الجزء الثاني — التزامن - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-Java

دورة حياة الخيط

على مدار حياته، تتغير حالة الخيط. في الواقع، تشمل هذه التغييرات دورة حياة الخيط. بمجرد إنشاء الموضوع، تصبح حالته جديدة. في هذه الحالة، لا يتم تشغيل مؤشر الترابط الجديد بعد، ولا يعرف برنامج جدولة سلاسل رسائل Java أي شيء عنه بعد. لكي يتعرف برنامج جدولة الخيط على الخيط، يجب عليك استدعاء الأسلوب thread.start(). ثم سينتقل الخيط إلى حالة RUNNABLE. يحتوي الإنترنت على الكثير من المخططات غير الصحيحة التي تميز بين الحالات "القابلة للتشغيل" و"الجارية". ولكن هذا خطأ، لأن Java لا تميز بين "جاهز للعمل" (قابل للتشغيل) و"العمل" (قيد التشغيل). عندما يكون الخيط حيًا ولكنه غير نشط (غير قابل للتشغيل)، فإنه يكون في إحدى الحالتين:
  • محظور - في انتظار دخول قسم حرج، أي synchronizedكتلة.
  • الانتظار - انتظار مؤشر ترابط آخر لتلبية بعض الشروط.
إذا تم استيفاء الشرط، فسيقوم برنامج جدولة الخيط ببدء الخيط. إذا كان الخيط ينتظر وقتًا محددًا، فإن حالته هي TIMED_WAITING. إذا لم يعد مؤشر الترابط قيد التشغيل (انتهى أو تم طرح استثناء)، فإنه يدخل في حالة الإنهاء. لمعرفة حالة الخيط، استخدم getState()الطريقة. المواضيع لديها أيضًا isAlive()طريقة تُرجع صحيحًا إذا لم يتم إنهاء الموضوع.

LockSupport ومواقف السيارات الموضوع

بدءًا من Java 1.6، ظهرت آلية مثيرة للاهتمام تسمى LockSupport . أفضل معًا: Java وفئة Thread.  الجزء الثاني – التزامن – 12تقوم هذه الفئة بربط "التصريح" بكل مؤشر ترابط يستخدمه. يعود استدعاء الأسلوب park()فورًا إذا كان التصريح متاحًا، مما يؤدي إلى استهلاك التصريح في العملية. وإلا فإنه يمنع. يؤدي استدعاء unparkالطريقة إلى جعل التصريح متاحًا إذا لم يكن متاحًا بعد. لا يوجد سوى تصريح واحد. تشير وثائق Java LockSupportإلى Semaphoreالفئة. دعونا نلقي نظرة على مثال بسيط:
import java.util.concurrent.Semaphore;
public class HelloWorldApp{

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
سينتظر هذا الرمز دائمًا، لأن الإشارة الآن لديها 0 تصاريح. وعندما acquire()يتم استدعاء الكود (أي طلب التصريح)، ينتظر الخيط حتى يتلقى التصريح. وبما أننا ننتظر، يجب علينا التعامل مع InterruptedException. ومن المثير للاهتمام أن الإشارة تحصل على حالة خيط منفصلة. إذا نظرنا إلى JVisualVM، فسنرى أن الحالة ليست "انتظر"، بل "بارك". أفضل معًا: Java وفئة Thread.  الجزء الثاني – التزامن – 13دعونا ننظر إلى مثال آخر:
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());

        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
ستكون حالة مؤشر الترابط في انتظار، لكن JVisualVM يميز بين waitالكلمة synchronizedالأساسية parkوالفئة LockSupport. لماذا هذا LockSupportبغاية الأهمية؟ نعود مرة أخرى إلى وثائق Java وننظر إلى حالة مؤشر ترابط الانتظار . كما ترون، هناك ثلاث طرق فقط للوصول إليها. اثنان من هذه الطرق هما wait()و join(). والثالث هو LockSupport. في Java، يمكن أيضًا إنشاء الأقفال على LockSupport وتقديم أدوات ذات مستوى أعلى. دعونا نحاول استخدام واحد. على سبيل المثال، ألق نظرة على ReentrantLock:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
كما هو الحال في الأمثلة السابقة، كل شيء بسيط هنا. ينتظر الكائن lockأن يقوم شخص ما بتحرير المورد المشترك. إذا نظرنا إلى JVisualVM، فسنرى أن الخيط الجديد سيتم إيقافه حتى mainيقوم الخيط بتحرير القفل إليه. يمكنك قراءة المزيد حول الأقفال هنا: Java 8 StampedLocks vs. ReadWriteLocks وSynchronized and Lock API في Java. لفهم كيفية تنفيذ الأقفال بشكل أفضل، من المفيد أن تقرأ عن Phaser في هذه المقالة: دليل Java Phaser . وبالحديث عن المزامنات المختلفة، يجب عليك قراءة مقالة DZone على The Java Synchronizers.

خاتمة

في هذه المراجعة، قمنا بفحص الطرق الرئيسية التي تتفاعل بها سلاسل الرسائل في Java. مواد اضافية: أفضل معًا: Java وفئة Thread. الجزء الأول - خيوط التنفيذ أفضل معًا: Java وفئة Thread. الجزء الثالث - التفاعل بشكل أفضل معًا: Java وفئة Thread. الجزء الرابع - الأشخاص القابلون للاستدعاء والمستقبل والأصدقاء أفضل معًا: Java وفئة Thread. الجزء الخامس — Executor، ThreadPool، Fork/Join Better Together: Java وفئة Thread. الجزء السادس – أطلق النار بعيدًا!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION