المؤقت هو واجهة برمجة تطبيقات مبكرة في JDK. عادةً ما نستخدم Timer و TimerTask للقيام بالمهام مع التأخير والدورة قبل خروج NewsChedTheRedThreadPool ، لكن الموقت لديه بعض العيوب. لماذا نقول ذلك؟
يقوم المؤقت بإنشاء مؤشرات ترابط فريدة فقط لأداء جميع مهام المؤقت. إذا كان تنفيذ مهمة الموقت يستغرق وقتًا طويلاً ، فسيؤدي ذلك إلى حدوث مشاكل مع دقة التوقيت الأخرى. على سبيل المثال ، يتم تنفيذ TimerTask كل 10 ثوانٍ ، ويتم تنفيذ TimerTask آخر كل 40 مللي ثانية. سيتم استدعاء المهام المتكررة 4 مرات على التوالي بعد اكتمال المهام اللاحقة ، أو أن 4 مكالمات "ضائعة" تمامًا. مشكلة أخرى في المؤقت هي أنه إذا قامت TimerTask بإلقاء استثناء غير محدد ، فسيقوم بإنهاء مؤشر ترابط المؤقت. في هذه الحالة ، لن يرد المؤقت على تنفيذ مؤشر الترابط مرة أخرى ؛ يعتقد خطأً أنه تم إلغاء المؤقت بأكمله. لن يتم تنفيذ TimerTask ، التي تم تحديد موعدها ولكن لم يتم تنفيذها بعد ، مرة أخرى ، ولا يمكن تحديد موعد مهام جديدة.
لقد قمت هنا بعمل عرض تجريبي صغير لإعادة إنتاج المشكلة ، والرمز هو كما يلي:
package com.hjc ؛ import java.util.timer ؛ استيراد java.util.timertask ؛/*** تم إنشاؤه بواسطة Cong في 2018/7/12. */الفئة العامة TimerTest {// إنشاء Timer Object Static Timer Timer = New Timer () ؛ public static void main (string [] args) {// إضافة المهمة 1 ، تأخير تنفيذ مؤقت. ") ؛}} ، 500) ؛ // أضف المهمة 2 ، تأخير تنفيذ Timer.Schedule (New TimerTask () {Override public void run () {for (؛) {system.out.println ("-اثنين من المهام ---") ؛ جرب {thread.sleep (1000) ؛ }} ، 1000) ؛ }}كما ذكر أعلاه ، تمت إضافة مهمة للتنفيذ لأول مرة بعد 500 مللي ثانية ، ثم تمت إضافة المهمة الثانية إلى التنفيذ بعد 1S. ما نتوقعه هو أنه عندما يتم إخراج المهمة الأولى --- مهمة واحدة --- تنتظر 1s ، ستخرج المهمة الثانية-Two --- ،
ومع ذلك ، بعد تنفيذ الرمز ، يكون الإخراج كما يلي:
مثال 2 ،
فئة عامة shedule {static static start start ؛ public static void main (string [] args) {timertask task = new timertask () {public void run () {system.out.println (System.CurrentTimeMillis ()-start) ؛ حاول {thread.sleep (3000) ؛ } catch (interruptedException e) {E.PrintStackTrace () ؛ }}} ؛ TimerTask Task1 = New TimerTask () {Override public void run () {system.out.println (System.CurrentTimeMillis ()-start) ؛ }} ؛ Timer Timer = New Timer () ؛ start = system.currentTimeMillis () ؛ // ابدأ مهمة مجدولة ، تنفيذ Timer.Schedule (المهمة ، 1000) ؛ // ابدأ مهمة مجدولة ، تنفيذ Timer.Schedule (Task1،3000) ؛ }}نتوقع أن يتم تنفيذ البرنامج أعلاه بعد تنفيذ المهمة الأولى بعد أن تكون المهمة الثانية 3s ، أي إخراج 1000 وواحد 3000.
نتائج التشغيل الفعلية هي كما يلي:
نتائج التشغيل الفعلية ليست كما نود. نتيجة العالم هي أن المهمة الثانية هي الإخراج بعد 4S ، أي 4001 حوالي 4 ثوان. أين ذهب هذا الجزء من الوقت؟ كان ذلك الوقت يشغله نوم مهمتنا الأولى.
الآن نقوم بإزالة thread.sleep () في المهمة الأولى ؛ هل يعمل هذا السطر من الكود بشكل صحيح؟ نتائج التشغيل كما يلي:
يمكن ملاحظة أن المهمة الأولى يتم تنفيذها بعد 1s ، ويتم تنفيذ المهمة الثانية بعد 3s بعد تنفيذ المهمة الأولى.
هذا يعني أن المؤقت يخلق فقط موضوعًا فريدًا لأداء جميع مهام المؤقت. إذا كان تنفيذ مهمة الموقت يستغرق وقتًا طويلاً ، فسيؤدي ذلك إلى حدوث مشاكل مع دقة التوقيت الأخرى.
تحليل مبدأ تنفيذ المؤقت
فيما يلي مقدمة موجزة لمبدأ المؤقت. الرقم التالي هو مقدمة لنموذج الموقت:
1.TaskQueue هو قائمة انتظار ذات أولوية تنفذها كومة شجرة ثنائية متوازنة ، ويحتوي كل كائن مؤقت على قائمة انتظار فريدة من نوعها. تتمثل طريقة الجدول الزمني لمؤشر استدعاء مؤشر ترابط المستخدم في إضافة مهمة TimerTask إلى قائمة انتظار TaskQueue. عند استدعاء طريقة الجدول ، يتم استخدام معلمة التأخير الطويلة للإشارة إلى المدة التي يتم تأخير المهمة لتنفيذها.
2. المؤقت هو الخيط الذي يؤدي مهمة محددة. يحصل على المهمة بأقل أولوية من قائمة انتظار TaskQueue للتنفيذ. تجدر الإشارة إلى أنه فقط بعد تنفيذ المهمة الحالية ، سيتم الحصول على المهمة التالية من قائمة الانتظار. بغض النظر عما إذا كان هناك وقت تأخير محدد في قائمة الانتظار ، فإن المؤقت يحتوي فقط على مؤشر ترابط مؤقت واحد ، لذلك يمكن ملاحظة أن التنفيذ الداخلي للوقت هو نموذج واحد مستهلك متعدد المنتجات.
من نموذج التنفيذ ، يمكننا أن نعرف أنه لاستكشاف المشكلة أعلاه ، ما عليك سوى النظر في تنفيذ TimerThread. رمز المصدر المنطقي الرئيسي لطريقة تشغيل المؤقت هو كما يلي:
public void run () {try {mainloop () ؛ } أخيرًا {// شخص قتل هذا الموضوع ، يتصرف كما لو أن المؤقت قد ألغى متزامن (قائمة الانتظار) {newtasksmaybescheduled = false ؛ queue.clear () ؛ // القضاء على المراجع القديمة}}} private void mainloop () {بينما (صحيح) {try {timertask task ؛ المنطقية المهمة ؛ // lock synchronized (Queue) {......} if (taskfired) task.run () ؛ // تنفيذ المهمة} catch (interruptedException e) {}}}يمكن ملاحظة أنه عندما يتم إلقاء استثناء غير InterruptedException أثناء تنفيذ المهمة ، سيتم إنهاء مؤشر ترابط المستهلك الوحيد بسبب إلقاء استثناء ، وسيتم تنفيذ المهام الأخرى التي سيتم تنفيذها في قائمة الانتظار. لذلك ، من الأفضل استخدام بنية المحاولة للاستمتاع بالاستثناءات الرئيسية الممكنة في طريقة تشغيل TimerTask ، ولا ترمي الاستثناءات خارج طريقة التشغيل.
في الواقع ، لتنفيذ وظائف مشابهة لـ Timer ، إنه خيار أفضل لاستخدام جدول ScredulThreadPoolexecutor. إحدى المهام في SecretThraSeReadPoolexecutor ترمي استثناء ، ولا تتأثر المهام الأخرى.
مثال ScurdeThReadPoolexecutor هو كما يلي:
/*** تم إنشاؤه بواسطة Cong في 2018/7/12. */الفئة العامة ScreedThReadPoolExecutort {static SecretThreadPoolExecutor SecrledThreadPoolExecutor = جديد ScreadThreadPoolexecutor (1) ؛ public static void main (string [] args) {SecrettHreadPoolExecutor.Schedule (new RunNable () {public void run () {system.out.println ("-on thore ---") ؛ try {thread.sleep (1000) ؛ }} ، 500 ، timeUnit.microseconds) ؛ ScredultHreadPoolexecutor.Schedule (new RunNable () {public void run () {for (int i = 0 ؛ i <5 ؛ ++ i) {system.out.println ("-اثنين ---") timeunit.microseconds) ؛ SchedulthReadPoolexecutor.shutdown () ؛ }}نتائج التشغيل كما يلي:
لا يتأثر السبب في أن المهام الأخرى لـ SecretThreadPoolexecutor لا تتأثر بالمهمة التي ترمي الاستثناءات هو أن الصيد يسقط الاستثناء في مهمة ScheduledFutureTask في ScredulThreadPoolexecutor ، ولكن من الأفضل استخدام الممارسات للقبض على الاستثناءات وطباعة سجلات مجموعة الخيوط.