الخيط هو الوحدة الأساسية لتشغيل نظام التشغيل، وهو مغلف في عملية واحدة. حتى لو لم نقم بإنشاء مؤشر ترابط يدويًا، فسيكون للعملية مؤشر ترابط افتراضي قيد التشغيل.
بالنسبة لـ JVM، عندما نكتب برنامجًا أحادي الخيط للتشغيل، هناك خيطان على الأقل يعملان في JVM، أحدهما هو البرنامج الذي أنشأناه، والآخر هو جمع البيانات المهملة.
المعلومات الأساسية للخيط
يمكننا الحصول على بعض المعلومات حول الموضوع الحالي من خلال طريقة Thread.currentThread() وتعديلها.
دعونا نلقي نظرة على الكود التالي:
Thread.currentThread().setName("اختبار");
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
name = Thread.currentThread().getName();
الأولوية = Thread.currentThread().getPriority();
groupName = Thread.currentThread().getThreadGroup().getName();
isDaemon = Thread.currentThread().isDaemon();
System.out.println("اسم الموضوع:" + name);
System.out.println("الأولوية:" + الأولوية);
GroupName ، سيكون كل مؤشر ترابط في مجموعة سلاسل الرسائل بشكل افتراضي، ويمكننا أيضًا إنشاء مجموعة سلاسل رسائل بشكل صريح.
الاسم ، سيكون لكل موضوع اسم إذا لم يتم تحديده بشكل صريح، فإن قاعدة الاسم هي "Thread-xxx".
الأولوية ، سيكون لكل مؤشر ترابط أولويته الخاصة، وطريقة JVM في التعامل مع الأولوية هي "استباقية". عندما يعثر JVM على خيط ذو أولوية عالية، فإنه يقوم على الفور بتشغيل الخيط لسلاسل رسائل متعددة ذات أولويات متساوية، ويستقصيها JVM. تتراوح أولوية مؤشر الترابط في Java من 1 إلى 10، والقيمة الافتراضية هي 5. تحدد فئة مؤشر الترابط ثوابتين: MIN_PRIORITY وMAX_PRIORITY لتمثيل الأولويات الأعلى والأدنى.
يمكننا إلقاء نظرة على الكود التالي، الذي يحدد خيطين لهما أولويات مختلفة:
موضوع thread2 = موضوع جديد ("عالية")
{
تشغيل الفراغ العام ()
{
لـ (int i = 0; i < 5; i++)
{
System.out.println("الخيط 2 قيد التشغيل.");
}
}
};
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
Thread1.start();
Thread2.start();
}
Thread1.start();
}
كيفية إنشاء موضوع
يوضح المحتوى أعلاه بعض المعلومات في سلسلة الرسائل الافتراضية، فكيف يتم إنشاء سلسلة رسائل؟ في Java، لدينا 3 طرق لإنشاء سلاسل الرسائل.
المواضيع في Java إما ترث فئة Thread أو تنفذ الواجهة القابلة للتشغيل.
استخدم الفئات الداخلية لإنشاء المواضيع
يمكننا استخدام الفئات الداخلية لإنشاء سلاسل العمليات. تتمثل العملية في الإعلان عن متغير من النوع Thread وتجاوز طريقة التشغيل. رمز العينة كما يلي:
يمكننا استخلاص فئة من Thread وتجاوز طريقة التشغيل الخاصة بها بطريقة مشابهة لما ورد أعلاه. رمز العينة كما يلي:
الفراغ الثابت العام createThreadBySubClass()
{
MyThread thread = new MyThread();
Thread.start();
}
يمكننا تحديد فئة لتنفيذ الواجهة القابلة للتشغيل، ثم استخدام مثيل لهذه الفئة كمعلمة لبناء مُنشئ متغير الموضوع. رمز العينة كما يلي:
الفراغ العام الثابت createThreadByRunnable()
{
MyRunnable runnable = new MyRunnable();
موضوع الموضوع = موضوع جديد (قابل للتشغيل)؛
Thread.start();
}
يتضمن ذلك وضع تشغيل مؤشرات الترابط المتعددة في Java، عندما يتم تشغيل مؤشرات الترابط المتعددة، توجد اختلافات بين "مؤشرات الترابط المتعددة للكائنات المتعددة" و"مؤشرات الترابط المتعددة للكائنات الفردية":
Multi-object multi-threading ، يقوم البرنامج بإنشاء كائنات ترابط متعددة أثناء التشغيل، ويتم تشغيل خيط واحد على كل كائن.
كائن واحد متعدد الخيوط ، يقوم البرنامج بإنشاء كائن مؤشر ترابط أثناء التشغيل وتشغيل مؤشرات ترابط متعددة عليه.
من الواضح أنه من منظور مزامنة وجدولة الخيط، فإن تعدد الكائنات المتعددة يكون أبسط. من بين طرق إنشاء الخيوط الثلاثة المذكورة أعلاه، أول طريقتين هما "سلسلة ترابط متعددة الكائنات"، والثالثة يمكن أن تستخدم إما "سلسلة ترابط متعددة الكائنات" أو "خيط مفرد لكائن واحد".
دعونا نلقي نظرة على نموذج التعليمات البرمجية التالي، والذي سيستخدم أسلوب Object.notify، وسوف يقوم هذا الأسلوب بتنبيه مؤشر ترابط على الكائن؛ وسوف يقوم أسلوب Object.notifyAll بتنبيه جميع مؤشرات الترابط الموجودة على الكائن.
يرمي الفراغ الرئيسي العام (String[] args) InterruptedException
{
notifyTest();
notifyTest2();
notifyTest3();
}
يرمي notifyTest () الفراغ الثابت الخاص InterruptedException
{
MyThread[] arrThreads = new MyThread[3];
لـ (int i = 0; i < arrThreads.length; i++)
{
arrThreads[i] = new MyThread();
arrThreads[i].id = i;
arrThreads[i].setDaemon(true);
arrThreads[i].start();
}
Thread.sleep(500);
لـ (int i = 0; i < arrThreads.length; i++)
{
متزامن (arrThreads[i])
{
arrThreads[i].notify();
}
}
}
يلقي notifyTest2 () الفراغ الثابت الخاص InterruptedException
{
MyRunner[] arrMyRunners = new MyRunner[3];
Thread[] arrThreads = new Thread[3];
لـ (int i = 0; i < arrThreads.length; i++)
{
arrMyRunners[i] = new MyRunner();
arrMyRunners[i].id = i;
arrThreads[i] = new Thread(arrMyRunners[i]);
arrThreads[i].setDaemon(true);
arrThreads[i].start();
}
Thread.sleep(500);
لـ (int i = 0; i < arrMyRunners.length; i++)
{
متزامن (arrMyRunners[i])
{
arrMyRunners[i].notify();
}
}
}
يلقي notifyTest3 () الفراغ الثابت الخاص InterruptedException
{
MyRunner runner = new MyRunner();
Thread[] arrThreads = new Thread[3];
لـ (int i = 0; i < arrThreads.length; i++)
{
arrThreads[i] = new Thread(runner);
arrThreads[i].setDaemon(true);
arrThreads[i].start();
}
Thread.sleep(500);
متزامن (عداء)
{
runner.notifyAll();
}
}
}
فئة MyThread تمتد الموضوع
{
معرف int العام = 0؛
تشغيل الفراغ العام ()
{
System.out.println("الخيط " + id + " جاهز للنوم لمدة 5 دقائق.");
يحاول
{
متزامنة (هذا)
{
this.wait(5*60*1000);
}
}
قبض على (InterruptedException على سبيل المثال)
{
ex.printStackTrace();
}
System.out.println("تم إيقاظ الخيط "th" + id + ".");
}
}
تقوم فئة MyRunner بتنفيذ Runnable
{
معرف int العام = 0؛
تشغيل الفراغ العام ()
{
System.out.println("الخيط " + id + " جاهز للنوم لمدة 5 دقائق.");
يحاول
{
متزامنة (هذا)
{
this.wait(5*60*1000);
}
}
قبض على (InterruptedException على سبيل المثال)
{
ex.printStackTrace();
}
System.out.println("تم إيقاظ الخيط "th" + id + ".");
}
}
تعد طريقة notifyAll مناسبة لسيناريو "كائن واحد متعدد الخيوط"، لأن طريقة الإخطار ستقوم فقط بتنبيه مؤشر ترابط واحد بشكل عشوائي على الكائن.
تبديل حالة الموضوع
بالنسبة للخيط، من وقت إنشائه حتى انتهاء الخيط، قد تكون حالة الخيط أثناء هذه العملية كما يلي:
الإنشاء: يوجد بالفعل مثيل لمؤشر الترابط، لكن وحدة المعالجة المركزية لا تزال لديها الموارد وشرائح الوقت المخصصة لها.
جاهز: حصل مؤشر الترابط على جميع الموارد اللازمة للتشغيل وينتظر فقط أن تقوم وحدة المعالجة المركزية بجدولة الوقت.
قيد التشغيل: يقع مؤشر الترابط في شريحة وقت وحدة المعالجة المركزية الحالية ويقوم بتنفيذ المنطق ذي الصلة.
Sleep: بشكل عام الحالة بعد استدعاء Thread.sleep في هذا الوقت، لا يزال مؤشر الترابط يحتفظ بموارد مختلفة مطلوبة للتشغيل، ولكن لن تتم جدولتها بواسطة وحدة المعالجة المركزية.
تعليق: بشكل عام، الحالة بعد استدعاء Thread.suspend، تشبه حالة السكون، ولن تقوم وحدة المعالجة المركزية بجدولة الخيط، والفرق هو أنه في هذه الحالة، سيحرر الخيط جميع الموارد.
الموت: ينتهي مؤشر الترابط أو يتم استدعاء الأسلوب Thread.stop.
بعد ذلك سنوضح كيفية تبديل حالات مؤشر الترابط أولاً، سنستخدم الطريقة التالية:
Thread() أو Thread(Runnable): قم بإنشاء موضوع.
Thread.start: بدء موضوع.
Thread.sleep: تبديل الخيط إلى حالة السكون.
Thread.interrupt: مقاطعة تنفيذ الخيط.
Thread.join: انتظر حتى ينتهي الموضوع.
Thread.yield: حرمان الخيط من شريحة وقت التنفيذ الخاصة به على وحدة المعالجة المركزية وانتظر الجدولة التالية.
Object.wait: قفل جميع سلاسل الرسائل الموجودة على الكائن ولن يستمر في التشغيل حتى طريقة الإشعار.
Object.notify: تنبيه مؤشر ترابط على الكائن بشكل عشوائي.
Object.notifyAll: تنبيه كافة المواضيع الموجودة على الكائن.
الآن، حان وقت العرض التوضيحي! ! !
موضوع الانتظار والاستيقاظ
يتم استخدام أساليب Object.wait وObject.notify هنا بشكل أساسي، يرجى الاطلاع على نسخة الإخطار أعلاه. تجدر الإشارة إلى أن كلا من الانتظار والإخطار يجب أن يستهدفا نفس الكائن. عندما نقوم بإنشاء مؤشر ترابط من خلال تنفيذ الواجهة القابلة للتشغيل، يجب علينا استخدام هاتين الطريقتين على الكائن القابل للتشغيل بدلاً من كائن الموضوع.
خيط النوم والاستيقاظ
يرمي الفراغ الرئيسي العام (String[] args) InterruptedException
{
اختبار النوم();
}
يلقي اختبار SleepTest () الخاص الفراغي الثابت InterruptedException
{
موضوع الموضوع = موضوع جديد ()
{
تشغيل الفراغ العام ()
{
System.out.println("Thread" + Thread.currentThread().getName() + "سوف ينام لمدة 5 دقائق.");
يحاول
{
Thread.sleep(5*60*1000);
}
قبض على (InterruptedException على سبيل المثال)
{
System.out.println("Thread" + Thread.currentThread().getName() + "انقطع النوم.");
}
System.out.println("Thread" + Thread.currentThread().getName() + "انتهى السكون.");
}
};
thread.setDaemon(true);
Thread.start();
Thread.sleep(500);
Thread.interrupt();
}
}
على الرغم من وجود طريقة Thread.stop، إلا أنه لا يُنصح باستخدام هذه الطريقة، يمكننا استخدام آلية السكون والاستيقاظ المذكورة أعلاه للسماح للخيط بإنهاء الخيط عند معالجة IterruptedException.
يرمي الفراغ الرئيسي العام (String[] args) InterruptedException
{
stopTest();
}
يرمي stopTest () الفراغ الثابت الخاص InterruptedException
{
موضوع الموضوع = موضوع جديد ()
{
تشغيل الفراغ العام ()
{
System.out.println("الخيط قيد التشغيل.");
يحاول
{
Thread.sleep(1*60*1000);
}
قبض على (InterruptedException على سبيل المثال)
{
System.out.println("مقاطعة الخيط، نهاية الخيط");
يعود؛
}
System.out.println("انتهى الموضوع بشكل طبيعي.");
}
};
Thread.start();
Thread.sleep(500);
Thread.interrupt();
}
}
عندما نقوم بإنشاء 10 سلاسل فرعية في الخيط الرئيسي، ثم نتوقع أنه بعد اكتمال جميع سلاسل الرسائل الفرعية العشرة، سينفذ الخيط الرئيسي المنطق التالي. في هذا الوقت، حان الوقت لظهور Thread.join.
يرمي الفراغ الرئيسي العام (String[] args) InterruptedException
{
joinTest();
}
يرمي joinTest () الفراغ الثابت الخاص InterruptedException
{
موضوع الموضوع = موضوع جديد ()
{
تشغيل الفراغ العام ()
{
يحاول
{
ل(int i = 0; i < 5; i++)
{
System.out.println("الخيط قيد التشغيل.");
Thread.sleep(1000);
}
}
قبض على (InterruptedException على سبيل المثال)
{
ex.printStackTrace();
}
}
};
thread.setDaemon(true);
Thread.start();
Thread.sleep(1000);
Thread.join();
System.out.println("انتهى الموضوع الرئيسي بشكل طبيعي.");
}
}
نحن نعلم أن جميع المواضيع ضمن العملية تشترك في مساحة الذاكرة، فكيف ننقل الرسائل بين المواضيع المختلفة؟ عند مراجعة Java I/O، تحدثنا عن PipedStream وPipedReader، وهنا يأتي دورهما.
المثالان التاليان لهما نفس الوظائف تمامًا، والفرق هو أن أحدهما يستخدم الدفق والآخر يستخدم القارئ/الكاتب.
موضوع الموضوع 1 = موضوع جديد ()
{
تشغيل الفراغ العام ()
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
يحاول
{
بينما (صحيح)
{
رسالة السلسلة = br.readLine();
pos.write(message.getBytes());
إذا (message.equals("end")) استراحة؛
}
br. Close();
pos. Close();
}
قبض (استثناء على سبيل المثال)
{
ex.printStackTrace();
}
}
};
الموضوع thread2 = الموضوع الجديد ()
{
تشغيل الفراغ العام ()
{
بايت[] المخزن المؤقت = بايت جديد[1024];
int بايت القراءة = 0؛
يحاول
{
while((bytesRead = pis.read(buffer, 0, buffer.length)) != -1)
{
System.out.println(new String(buffer));
if (new String(buffer).equals("end"))break;
المخزن المؤقت = فارغ؛
المخزن المؤقت = بايت جديد[1024];
}
بيس.كلوز();
المخزن المؤقت = فارغ؛
}
قبض (استثناء على سبيل المثال)
{
ex.printStackTrace();
}
}
};
thread1.setDaemon(true);
thread2.setDaemon(true);
Thread1.start();
Thread2.start();
Thread1.join();
Thread2.join();
}
موضوع الموضوع 1 = موضوع جديد ()
{
تشغيل الفراغ العام ()
{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
يحاول
{
بينما (صحيح)
{
رسالة السلسلة = br.readLine();
bw.write(message);
bw.newLine();
bw.flush();
إذا (message.equals("end")) استراحة؛
}
br. Close();
pw.Close();
bw. Close();
}
قبض (استثناء على سبيل المثال)
{
ex.printStackTrace();
}
}
};
الموضوع thread2 = الموضوع الجديد ()
{
تشغيل الفراغ العام ()
{
خط السلسلة = فارغ؛
يحاول
{
بينما ((السطر = br.readLine()) != فارغ)
{
System.out.println(line);
إذا (line.equals("end")) استراحة؛
}
br. Close();
pr.Close();
}
قبض (استثناء على سبيل المثال)
{
ex.printStackTrace();
}
}
};
thread1.setDaemon(true);
thread2.setDaemon(true);
Thread1.start();
Thread2.start();
Thread1.join();
Thread2.join();
}