تعتمد هذه السلسلة على مسار تحسين الأرقام في الذهب ، ومن أجل التعلم بشكل أفضل ، تم إجراء سلسلة من السجلات. تقدم هذه المقالة بشكل أساسي 1. ما هو مؤشر ترابط 2. العمليات الأساسية للمواضيع 3. مؤشر ترابط الخفي 4. أولوية مؤشر الترابط 5.
1. ما هو الموضوع
المواضيع هي وحدات تنفيذ ضمن العمليات
هناك العديد من المواضيع في عملية.
الموضوع هو وحدة تنفيذ داخل العملية.
سبب استخدام المواضيع هو أن عملية تبديل العملية هي عملية ثقيلة للغاية وتستهلك الموارد. إذا كنت تستخدم عمليات متعددة ، فلن يكون عدد التزامن مرتفعًا جدًا. المواضيع هي وحدات جدولة أصغر وأخف وزنا ، لذلك يتم استخدام المواضيع على نطاق واسع في التصميم المتزامن.
في Java ، يشبه مفهوم المواضيع مفهوم المواضيع على مستوى نظام التشغيل. في الواقع ، ستقوم JVM بتخطيط المواضيع في Java إلى منطقة خيط نظام التشغيل.
2. العمليات الأساسية للموضوعات
2.1 مخطط حالة الموضوع
تعرض الصورة أعلاه التشغيل الأساسي للمواضيع في Java.
عندما يكون الخيط جديدًا ، لا يعمل الخيط في الواقع. إنه يولد كيانًا فقط ، ويتم تشغيل الخيط فعليًا عند استدعاء طريقة البدء في هذا المثيل. بعد بدء التشغيل ، يصل إلى حالة Runnable. Runnable يعني أن موارد الخيط وما إلى ذلك قد تم إعدادها ويمكن تنفيذها ، ولكن هذا لا يعني أنه في حالة التنفيذ. بسبب دوران شرائح الوقت ، قد لا يتم تنفيذ الخيط في هذا الوقت. بالنسبة لنا ، يمكن اعتبار الخيط قد تم تنفيذه ، ولكن ما إذا كان يتم تنفيذه يعتمد في الواقع على جدولة وحدة المعالجة المركزية المادية. عند تنفيذ مهمة الخيط ، يصل مؤشر الترابط إلى الحالة المنتهية.
في بعض الأحيان ، أثناء تنفيذ مؤشر ترابط ، من المحتم أن يتم تطبيق بعض الأقفال أو شاشات الكائنات. عندما لا يمكن استرداده ، سيتم حظر الخيط وتعليقه ويصل إلى الحالة المحظورة. إذا استدعى هذا الموضوع طريقة الانتظار ، فهو في حالة انتظار. سوف ينتظر الموضوع الذي يدخل حالة الانتظار حتى يخطرها مؤشرات الترابط الأخرى. بعد الإخطار ، ستتحول حالة الانتظار من حالة الانتظار إلى حالة Runnable لمواصلة التنفيذ. بالطبع ، هناك نوعان من حالات الانتظار ، أحدهما هو الانتظار إلى أجل غير مسمى حتى يتم إخطاره. لقد كان ينتظر فترة زمنية محدودة. على سبيل المثال ، إذا انتظرت 10 ثوانٍ أو لم يتم إخطارها ، فسيتم تبديلها تلقائيًا إلى الحالة القابلة للتشغيل.
2.2 إنشاء موضوع جديد
موضوع الموضوع = موضوع جديد () ؛
thread.start () ؛
هذا يفتح موضوع.
شيء واحد يجب ملاحظته
موضوع الموضوع = موضوع جديد () ؛
thread.run () ؛
لا يمكنك فتح موضوع جديد عن طريق استدعاء طريقة التشغيل مباشرة.
تستدعي طريقة البدء في الواقع طريقة التشغيل على مؤشر ترابط نظام تشغيل جديد. بمعنى آخر ، إذا قمت باستدعاء طريقة التشغيل مباشرة بدلاً من طريقة البدء ، فلن تبدأ سلسلة رسائل جديدة ، ولكنها ستؤدي عملياتك في تشغيل استدعاء مؤشرات الترابط الحالية.
Thread Thread = New Thread ("T1") {Override public void run () {// todo method method method system.out.println (thread.currentThread (). getName ()) ؛ }} ؛ thread.start () ؛ إذا تم استدعاء البداية ، فإن الإخراج هو T1Thread Thread = New Thread ("T1") {Override public void run () {// todo method method method system.out.println (thread.currentthread (). getName ()) ؛ }} ؛ thread.run () ؛ إذا تم تشغيله ، فإن الإخراج الرئيسي هو. (تشغيل التشغيل مباشرة هو في الواقع مجرد مكالمة وظيفية عادية ، ولم يحقق دور متعدد الخيوط)
هناك طريقتان لتنفيذ طريقة التشغيل
الطريقة الأولى هي تجاوز طريقة التشغيل مباشرة. كما هو موضح في الكود الآن ، يمكن تحقيق الطريقة الأكثر ملاءمة باستخدام فئة مجهولة.
Thread Thread = New Thread ("T1") {Override public void run () {// todo method method method system.out.println (thread.currentThread (). getName ()) ؛ }} ؛ الطريقة الثانية
الموضوع T1 = مؤشر ترابط جديد (CreateTheRead3 () جديد) ؛
CreateTheRead3 () ينفذ واجهة Runnable.
في فيديو Zhang Xiaoxiang ، يوصى بالطريقة الثانية ، قائلة إنها أكثر توجهاً نحو الكائن.
2.3 إنهاء الموضوع
لا ينصح Thread.stop (). يطلق جميع الشاشات
لقد تم ذكره بوضوح في الكود المصدري أن طريقة الإيقاف قد تم إهمالها ، ويتم شرح السبب أيضًا في Javadoc.
والسبب هو أن طريقة التوقف "عنيفة" للغاية. بغض النظر عن مكان تنفيذ الخيط ، سيتوقف على الفور من إسقاط الخيط.
عندما يحصل مؤشر ترابط الكتابة على القفل ، يبدأ كتابة البيانات. بعد كتابة معرف = 1 ، يتم إيقافه ويتم إصدار القفل عند التحضير لتعيين الاسم = 1. يحصل مؤشر ترابط القراءة على القفل لعملية القراءة. قراءة المعرف هي 1 والاسم لا يزال 0 ، مما يسبب تناقض البيانات.
الشيء الأكثر أهمية هو أن هذا النوع من الخطأ لن يلقي استثناء وسيكون من الصعب اكتشافه.
2.4 مقاطعة الموضوع
هناك 3 طرق لمقاطعة الموضوع
موضوع باطل عام.
thread boolean public.isinterrupted () //
خيط منطقي ثابت عام.
ما هو انقطاع الموضوع؟
إذا كنت لا تفهم آلية المقاطعة في Java ، فمن المحتمل جدًا أن يتسبب هذا التفسير في سوء فهم ، وتعتقد أن استدعاء طريقة المقاطعة للخيط سيقاطع بالتأكيد الخيط.
في الواقع ، انقطاع Java هو آلية تعاون. بمعنى آخر ، لا يقطع استدعاء طريقة المقاطعة لكائن مؤشر الترابط بالضرورة خيط التشغيل ، بل يتطلب فقط أن يقطع مؤشر الترابط نفسه في الوقت المناسب. كل مؤشر ترابط له حالة مقاطعة منطقية (وليس بالضرورة خاصية الكائن ، في الواقع ، هذه الحالة ليست في الواقع حقل مؤشر ترابط) ، وطريقة المقاطعة تضع الحالة ببساطة على صواب. بالنسبة إلى مؤشرات الترابط غير المحظورة ، يتم تغيير حالة المقاطعة ، أي ، thread.isinterrupted () سيعود صحيحًا ولن يوقف البرنامج ؛
public void run () {// thread t1 بينما (true) {thread.yield () ؛ }} t1.Interrupt () ؛ لن يكون لهذا أي تأثير لمقاطعة الخيط T1 ، ولكن يتم تغيير بتات حالة المقاطعة.
إذا كنت ترغب في إنهاء هذا الموضوع بأمان شديد ، فيجب عليك القيام بذلك
public void run () {بينما (true) {if (thread.currentThRead (). isInterrupted ()) {system.out.println ("interreted!") ؛ استراحة؛ } thread.yield () ؛ }}يوفر استخدام المقاطعات ضمانات معينة لاتساق البيانات.
بالنسبة إلى المواضيع في حالات الحظر القابلة للإلغاء ، مثل مؤشرات الترابط التي تنتظر هذه الوظائف ، thread.sleep () ، object.wait () ، و thread.join () ، سوف يرمي هذا الموضوع مقاطعًا بعد استلام إشارة المقاطعة ، وسيعيد حالة المقاطعة إلى خطأ.
بالنسبة للمواضيع في حالات إلغاء حظر ، يمكنك كتابة رمز مثل هذا:
public void run () {بينما (true) {if (thread.currentThRead (). isInterrupted ()) {system.out.println ("interreted!") ؛ استراحة؛ } جرب {thread.sleep (2000) ؛ } catch (interruptedException e) {system.out.println ("interreted at steam") ؛ // تعيين حالة المقاطعة. سيتم مسح جزء العلم المقاطعة بعد إلقاء موضوع استثناء. } thread.yield () ؛ }}2.5 خيط معلقة
تعليق المواضيع واستئنافها
لن يفرج عن القفل ()
في حالة حدوث القفل قبل السيرة الذاتية () ، فإن كلتا الطريقتين لحدوث طريق مسدود هما الطرق التي تم إهمالها ولا ينصح بها.
والسبب هو أن تعليق لا يطلق القفل ، لذلك لا يمكن لأي مؤشر ترابط الوصول إلى مورد المنطقة الحرج الذي يتم قفله حتى يتم استئنافه بواسطة مؤشرات ترابط أخرى. نظرًا لأنه لا يمكن التحكم في تسلسل سلسلة المواضيع ، إذا تم تشغيل طريقة السيرة الذاتية للمواضيع الأخرى أولاً ، فإن المعلقة لاحقًا ستشغل القفل دائمًا ، مما يتسبب في حدوث طريق مسدود.
استخدم الكود التالي لمحاكاة هذا السيناريو
اختبار الحزمة ؛ اختبار الفئة العامة {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 (). copend () ؛ }} public static void main (string [] args) remrows interruptedException {t1.start () ؛ thread.sleep (100) ؛ t2.start () ؛ t1.Resume () ؛ t2.resume () ؛ t1.join () ؛ t2.join () ؛ }} دع T1 و T2 يتنافسون على قفل في نفس الوقت ، يتم تعليق الخيط الذي يتم التنافس عليه ، ثم استئناف. من الناحية المنطقية ، يجب إصدار موضوع عن طريق السيرة الذاتية بعد التنافس عليه ، ثم يتنافس مؤشر ترابط آخر على القفل ويستأنف.
الناتج الناتج هو:
في T1
في T2
هذا يعني أن كلا الموضوعين يتنافسون على القفل ، لكن الضوء الأحمر على وحدة التحكم لا يزال قيد التشغيل ، مما يعني أنه يجب أن يكون هناك مؤشرات ترابط لم يتم تنفيذها في T1 و T2. دعونا نلقيها ونلقي نظرة
وقد وجد أنه تم تعليق T2. هذا يخلق طريق مسدود.
2.6 انضم و yeild
Yeild هي طريقة ثابتة أصلية. تتمثل هذه الطريقة في إصدار وقت وحدة المعالجة المركزية التي يمتلكها ثم التنافس مع مؤشرات الترابط الأخرى (لاحظ أن خيوط Yeild قد لا تزال تتنافس على وحدة المعالجة المركزية ، وانتبه إلى الفرق من النوم). يتم شرحه أيضًا في Javadoc أن yeild هي طريقة غير مستخدمة بشكل أساسي وتستخدم عمومًا في التصحيح والاختبار.
تعني طريقة الانضمام في انتظار انتهاء مؤشرات الترابط الأخرى ، تمامًا مثل الكود في قسم التعليق ، فأنت تريد أن ينتظر مؤشر الترابط الرئيسي T1 و T2 قبل الانتهاء. إذا لم ينتهي ، فسيتم حظر الخيط الرئيسي هناك.
اختبار الحزمة ؛ اختبار الفئة العامة {public platile static int i = 0 ؛ يمتد AddThread الفئة الثابتة العامة thread {Override public void run () {for (i = 0 ؛ i <10000000 ؛ i ++) ؛ }} public static void main (string [] args) remrows interruptedException {addThread at = new AddThread () ؛ at.start () ؛ at.join () ؛ system.out.println (i) ؛ }} إذا تمت إزالة at.join من الكود أعلاه ، فسيتم تشغيل الخيط الرئيسي مباشرة وستكون قيمة I صغيرة جدًا. إذا كان هناك انضمام ، فإن قيمة المطبوعة يجب أن تكون 10000000.
فكيف يتم تنفيذ الانضمام؟
طبيعة الانضمام
بينما (isalive ())
{
انتظر (0) ؛
}
يمكن أن تمر طريقة Join () أيضًا بوقت ، مما يعني انتظار فترة زمنية محدودة ، وتستيقظ تلقائيًا بعد هذا الوقت.
هناك سؤال مثل هذا: من سيخطر الموضوع؟ لا يوجد مكان للاتصال بالاتصال في فئة الموضوع؟
في Javadoc ، تم العثور على تفسير ذي صلة. عند اكتمال مؤشر ترابط وإنهائه ، سيتم استدعاء طريقة الإخطار لإيقاظ جميع مؤشرات الترابط في انتظار مثيل الخيط الحالي. تتم هذه العملية بواسطة JVM نفسها.
لذلك قدم لنا Javadoc اقتراحًا بعدم استخدام الانتظار وإخطار/إخطار مثيلات الخيط. نظرًا لأن JVM سوف يدعو نفسه ، فقد يكون الأمر مختلفًا عن النتيجة المتوقعة لمكالمتك.
3. خيط الخفي
يمكن فهم إكمال بعض الخدمات المنهجية في الخلفية ، مثل مؤشرات ترابط جمع القمامة وخيوط JIT ، على أنها مؤشرات الترابط الخفي.
عندما تنتهي جميع العمليات غير الدائرية داخل تطبيق Java ، ستخرج جهاز Java الظاهري بشكل طبيعي.
لقد كتبت مقالة حول كيفية تنفيذها في بيثون ، تحقق من ذلك هنا.
من السهل نسبيًا أن تصبح خفيًا في جافا.
الموضوع t = جديد daemont () ؛
T.SetDaemon (صواب) ؛
T.Start () ؛
هذا يفتح موضوع الخفي.
اختبار الحزمة ؛ اختبار الفئة العامة {public static class daemonthread يمتد Thread {Override public void run () {for (int i = 0 ؛ i <10000000 ؛ i ++) {system.out.println ("hi") ؛ }}} static void main (string [] args) يلقي interruptedException {daemonthread dt = new daemonthread () ؛ dt.start () ؛ }} عندما لا يكون Thread DT خيطًا خفيًا ، بعد التشغيل ، يمكننا رؤية إخراج وحدة التحكم مرحبًا
عند الانضمام قبل البدء
dt.setdaemon (صواب) ؛
تخرج وحدة التحكم مباشرة وليس لها إخراج.
4. أولوية الموضوع
هناك 3 متغيرات في فئة الخيط التي تحدد أولوية مؤشر الترابط.
int static int int int min_priority = 1 ؛ int static public static norm_priority = 5 ؛ int static static public int max_priority = 10 ؛ اختبار الحزمة ؛ اختبار الفئة العامة {الفئة الثابتة العامة العالية يمتد مؤشر الترابط {static int count = 0 ؛ Override public void run () {بينما (صحيح) {synchronized (test.class) {count ++ ؛ if (count> 10000000) {system.out.println ("High") ؛ استراحة؛ }}}}} الفئة الثابتة العامة المنخفضة تمتد مؤشر ترابط {static int count = 0 ؛ Override public void run () {بينما (صحيح) {synchronized (test.class) {count ++ ؛ if (count> 10000000) {system.out.println ("low") ؛ استراحة؛ }}}}} static void main (string [] args) remrows interruptedException {High High = new High () ؛ منخفض منخفض = جديد منخفض () ؛ High.SetPriority (thread.max_priority) ؛ low.setPriority (thread.min_priority) ؛ low.start () ؛ High.start () ؛ }} اسمح لخيط عالي الأولوية وخيط أولوية منخفضة التنافس على قفل في نفس الوقت ومعرفة أي واحد يكمل أولاً.
بالطبع ، لا يجب إكماله بالضرورة أولاً بأولوية عالية. بعد تشغيله عدة مرات ، وجدت أن احتمال الانتهاء من الأولوية العالية مرتفع نسبيًا ، ولكن لا يزال من الممكن إكماله أولاً بأولوية منخفضة.
5. عملية مزامنة الخيط الأساسية
متزامن و Object.wait () obejct.notify ()
للحصول على تفاصيل هذا القسم ، يرجى الرجوع إلى مدونة كتبت من قبل.
الشيء الرئيسي الذي يجب ملاحظته هو
هناك ثلاث طرق لقفل متزامن:
حدد كائن مغلق: قفل الكائن المحدد واحصل على قفل الكائن المحدد قبل إدخال رمز التزامن.
يتصرف مباشرة على طريقة المثيل: إنه يعادل قفل المثيل الحالي. قبل إدخال رمز التزامن ، يجب عليك الحصول على قفل المثيل الحالي.
يتصرف مباشرة على الطرق الثابتة: إنه يعادل قفل الفئة الحالية. قبل إدخال الكود المتزامن ، يجب عليك الحصول على قفل الفئة الحالية.
إذا كان يعمل على طريقة المثيل ، فلا يمكنك جديدين مثالين مختلفين
إنه يعمل على طرق ثابتة ، طالما أن الفصل هو نفسه ، لأن القفل المضافة عبارة عن فئة.
كيفية استخدام الانتظار والإخطار:
استخدم أي قفل ، اتصل بالانتظار والإخطار
لن أخوض في التفاصيل في هذه المقالة.