JavaRush /مدونة جافا /Random-AR /لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة
Viacheslav
مستوى

لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة

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

مقدمة

لذلك، نحن نعلم أن هناك مواضيع في جافا، والتي يمكنك أن تقرأ عنها في المراجعة " لا يمكنك إفساد جافا بموضوع: الجزء الأول - المواضيع ". هناك حاجة إلى خيوط للقيام بالعمل في وقت واحد. لذلك، من المحتمل جدًا أن تتفاعل الخيوط بطريقة أو بأخرى مع بعضها البعض. دعونا نفهم كيف يحدث هذا وما هي الضوابط الأساسية لدينا. لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة - 1

أَثْمَر

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

النوم - خيط النوم

قد ينام الخيط أثناء تنفيذه. هذا هو أبسط نوع من التفاعل مع المواضيع الأخرى. نظام التشغيل الذي تم تثبيت جهاز Java الظاهري عليه، حيث يتم تنفيذ تعليمات Java البرمجية، له برنامج جدولة سلاسل رسائل خاص به، يسمى Thread Scholer. هو الذي يقرر أي موضوع سيتم تشغيله ومتى. لا يمكن للمبرمج التفاعل مع هذا المجدول مباشرة من كود 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("Waked 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 بخيط: الجزء الثاني - المزامنة - 3كما ترون، دخل خيطنا في حالة السكون. في الواقع، يمكن إجراء نوم الخيط الحالي بشكل أكثر جمالا:
try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Waked up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
ربما لاحظت أننا نقوم بالمعالجة في كل مكان InterruptedException؟ دعونا نفهم لماذا.

مقاطعة موضوع أو Thread.interrupt

الشيء هو أنه أثناء انتظار الخيط في الحلم، قد يرغب شخص ما في مقاطعة هذا الانتظار. في هذه الحالة، نحن نتعامل مع مثل هذا الاستثناء. Thread.stopتم ذلك بعد الإعلان عن إهمال الطريقة ، أي. قديمة وغير مرغوب فيها للاستخدام. والسبب في ذلك هو أنه عندما تم استدعاء الطريقة، stopتم "قتل" الخيط ببساطة، وهو أمر لا يمكن التنبؤ به على الإطلاق. لم نتمكن من معرفة متى سيتم إيقاف التدفق، ولم نتمكن من ضمان اتساق البيانات. تخيل أنك تكتب البيانات إلى ملف ثم يتم إتلاف الدفق. لذلك، قررنا أنه سيكون من المنطقي أكثر عدم قتل التدفق، ولكن إبلاغه بأنه يجب أن ينقطع. كيفية الرد على هذا الأمر متروك للتدفق نفسه. يمكن العثور على مزيد من التفاصيل في " لماذا تم إهمال Thread.stop؟ " في Oracle. لنلقي نظرة على مثال:
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 ثانية، ولكننا سنطبع على الفور كلمة "Interrupted". وذلك لأننا أطلقنا على طريقة الخيط اسمًا 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 بخيط: الجزء الثاني - المزامنة - 4بفضل أدوات المراقبة، يمكنك رؤية ما يحدث مع الخيط. الطريقة joinبسيطة جدًا، لأنها ببساطة طريقة تحتوي على كود جافا يتم تنفيذها waitبينما يكون الخيط الذي يتم الاتصال به على قيد الحياة. بمجرد موت الخيط (عند الإنهاء)، يتم إنهاء الانتظار. هذا هو السحر كله للطريقة join. لذلك، دعونا ننتقل إلى الجزء الأكثر إثارة للاهتمام.

مراقب المفهوم

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

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

مقال من حبر مفيد جدًا هنا: " ولكن كيف يعمل تعدد مؤشرات الترابط؟ الجزء الأول: المزامنة ". من المفيد إضافة وصف إلى هذه المقالة من ملخص كتلة المهام من برنامج JDK bugtaker: " 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 محاولة إجراء تحسين يسمى "القفل المتحيز". سيحتوي عنوان الكائن في Mark Word على العلامة المقابلة وسجل الخيط المتصل به الشاشة. وهذا يقلل من الحمل عند التقاط الشاشة. إذا كانت الشاشة قد تم بالفعل ربطها بخيط آخر من قبل، فهذا القفل لا يكفي. يتحول JVM إلى نوع القفل التالي - القفل الأساسي. ويستخدم عمليات المقارنة والمبادلة (CAS). في الوقت نفسه، لم يعد الرأس في Mark Word يخزن Mark Word نفسه، ولكن تم تغيير الرابط لتخزينه + العلامة حتى يفهم JVM أننا نستخدم القفل الأساسي. إذا كان هناك تنافس على الشاشة لعدة سلاسل رسائل (أحدها قام بالتقاط الشاشة، والثاني في انتظار تحرير الشاشة)، فستتغير العلامة الموجودة في Mark Word، ويبدأ Mark Word في تخزين مرجع إلى الشاشة كـ كائن - بعض الكيانات الداخلية لـ JVM. كما هو مذكور في JEP، في هذه الحالة، هناك حاجة إلى مساحة في منطقة ذاكرة الكومة الأصلية لتخزين هذا الكيان. سيكون الرابط الخاص بموقع تخزين هذا الكيان الداخلي موجودًا في كائن Mark Word. وبالتالي، كما نرى، فإن الشاشة هي في الحقيقة آلية لضمان مزامنة وصول سلاسل العمليات المتعددة إلى الموارد المشتركة. هناك العديد من تطبيقات هذه الآلية التي يقوم JVM بالتبديل بينها. لذلك، من أجل البساطة، عندما نتحدث عن الشاشة، فإننا نتحدث في الواقع عن الأقفال. لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة - 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 بخيط: الجزء الثاني - المزامنة - 8كما ترون، الحالة في JVisualVM تسمى "Monitor" لأن مؤشر الترابط محظور ولا يمكن أن يشغل الشاشة. يمكنك أيضًا معرفة حالة مؤشر الترابط في الكود، لكن اسم هذه الحالة لا يتطابق مع مصطلحات JVisualVM، على الرغم من تشابهها. في هذه الحالة، ستعود th1.getState()الحلقة محظورة ، لأن أثناء تشغيل الحلقة، يكون الخيط مشغولاً بالشاشة، ويتم حظر الخيط ولا يمكنه الاستمرار في العمل حتى يتم إرجاع القفل. بالإضافة إلى كتل المزامنة، يمكن مزامنة الطريقة بأكملها. على سبيل المثال، طريقة من الفئة : forlockmainth1HashTable
public synchronized int size() {
	return count;
}
في وحدة زمنية واحدة، سيتم تنفيذ هذه الطريقة بواسطة مؤشر ترابط واحد فقط. لكننا بحاجة إلى قفل، أليس كذلك؟ نعم أنا أحتاجه. في حالة أساليب الكائن، سيكون القفل this. هناك مناقشة مثيرة للاهتمام حول هذا الموضوع: " هل هناك ميزة لاستخدام الطريقة المتزامنة بدلاً من الكتلة المتزامنة؟ ". إذا كانت الطريقة ثابتة، فلن يكون القفل this(نظرًا لأنه لا يمكن أن يكون للطريقة الثابتة this)، بل كائن فئة (على سبيل المثال، Integer.class).

انتظر وانتظر على الشاشة. طرق الإخطار والإخطار

يحتوي الخيط على طريقة انتظار أخرى، وهي متصلة بالشاشة. على عكس sleepو join، لا يمكن استدعاؤه فقط. واسمه هو wait. يتم تنفيذ الطريقة waitعلى الكائن الذي نريد انتظار شاشته. دعونا نرى مثالا:
public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // task будет ждать, пока его не оповестят через lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // После оповещения нас мы будем ждать, пока сможем взять лок
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // Ждём и после этого забираем себе лок, оповещаем и отдаём лок
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
في JVisualVM سيبدو الأمر كما يلي: لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة - 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يمكنك الحصول على حالة الموضوع. إذن، هذا مؤشر ترابط تم وضعه في قائمة الانتظار بواسطة الشاشة، وستكون الحالة في انتظار أو TIMED_WAITING إذا waitحددت الطريقة حدًا لوقت الانتظار. لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة - 11

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

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

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

منذ Java 1.6 كانت هناك آلية مثيرة للاهتمام تسمى LockSupport . لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة - 12تقوم هذه الفئة بربط "الإذن" أو الإذن بكل مؤشر ترابط يستخدمه. يعود استدعاء الأسلوب parkفورًا في حالة توفر تصريح، ويشغل نفس التصريح أثناء المكالمة. وإلا فإنه محظور. يؤدي استدعاء الطريقة unparkإلى جعل التصريح متاحًا إذا لم يكن متاحًا بالفعل. يوجد تصريح واحد فقط في Java API، 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) {
            // Просим разрешение и ждём, пока не получим его
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
سينتظر هذا الرمز إلى الأبد لأن الإشارة لديها الآن 0 تصريح. وعند استدعائه في التعليمات البرمجية acquire(أي طلب إذن)، ينتظر الخيط حتى يتلقى الإذن. وبما أننا ننتظر، فنحن ملزمون بمعالجتها InterruptedException. ومن المثير للاهتمام أن الإشارة تنفذ حالة مؤشر ترابط منفصلة. إذا نظرنا إلى JVisualVM، فسنرى أن حالتنا ليست الانتظار، ولكن بارك. لا يمكنك تدمير Java بخيط: الجزء الثاني - المزامنة - 13دعونا ننظر إلى مثال آخر:
public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            //Запаркуем текущий поток
            System.err.println("Will be Parked");
            LockSupport.park();
            // Как только нас распаркуют - начнём действовать
            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بين from synchronizedو parkfrom LockSupport. لماذا هذا واحد مهم جدا LockSupport؟ دعنا ننتقل مرة أخرى إلى Java API ونلقي نظرة على Thread State WAITING . كما ترون، هناك ثلاث طرق فقط للوصول إليها. طريقتان - هذا 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. الجزء الثاني. مزامنة الوصول إلى الكائنات القابلة للتغيير " و " Java Lock API. النظرية ومثال الاستخدام ." لفهم كيفية تنفيذ الأقفال بشكل أفضل، من المفيد أن تقرأ عن Phazer في النظرة العامة " Phaser Class ". وبالحديث عن المزامنات المختلفة، يجب عليك قراءة المقال عن حبري " Java.util.concurrent.* مرجع المزامنات ".

المجموع

في هذه المراجعة، نظرنا إلى الطرق الرئيسية التي تتفاعل بها سلاسل الرسائل في Java. مواد اضافية: # فياتشيسلاف
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION