ตัวจับเวลาเป็น API รุ่นแรกใน JDK เรามักจะใช้ตัวจับเวลาและ Timertask เพื่อทำงานที่มีความล่าช้าและเป็นระยะก่อนที่จะมีการกำหนดตารางเวลาใหม่ ๆ ออกมา แต่ตัวจับเวลามีข้อบกพร่องบางอย่าง ทำไมเราถึงพูดอย่างนั้น?
ตัวจับเวลาสร้างเธรดที่ไม่ซ้ำกันเท่านั้นเพื่อดำเนินงานตัวจับเวลาทั้งหมด หากการดำเนินงานของตัวจับเวลาใช้เวลานานมันจะทำให้เกิดปัญหากับความถูกต้องของ timertasks อื่น ๆ ตัวอย่างเช่น timertask หนึ่งจะดำเนินการทุก ๆ 10 วินาทีและ Timertask อื่นจะดำเนินการทุก ๆ 40ms งานซ้ำจะเรียกว่า 4 ครั้งติดต่อกันหลังจากงานที่ตามมาเสร็จสมบูรณ์หรือการโทร 4 ครั้งจะ "หายไป" อย่างสมบูรณ์ ปัญหาอีกประการหนึ่งเกี่ยวกับตัวจับเวลาคือถ้า Timertask โยนข้อยกเว้นที่ไม่ได้ตรวจสอบมันจะยุติเธรดตัวจับเวลา ในกรณีนี้ตัวจับเวลาจะไม่ตอบกลับการดำเนินการของเธรดอีกครั้ง มันเชื่อผิดว่าตัวจับเวลาทั้งหมดถูกยกเลิก Timertask ซึ่งได้รับการกำหนดเวลา แต่ยังไม่ได้ดำเนินการจะไม่ถูกดำเนินการอีกครั้งและไม่สามารถกำหนดงานใหม่ได้
ที่นี่ฉันได้ทำการสาธิตเล็ก ๆ เพื่อทำซ้ำปัญหารหัสมีดังนี้:
แพ็คเกจ com.hjc; นำเข้า java.util.timer; นำเข้า java.util.timertask;/*** สร้างโดย cong เมื่อปี 2018/7/12 */timertest คลาสสาธารณะ {// สร้างตัวจับเวลาตัวจับเวลาตัวจับเวลาตัวจับเวลาตัวจับเวลาสถิตตัวจับเวลา = ตัวจับเวลาใหม่ (); โมฆะคงที่สาธารณะหลัก (สตริง [] args) {// เพิ่มงาน 1, ล่าช้าการดำเนินการจับเวลา. schedule (timertask ใหม่ () {@Override โมฆะสาธารณะเรียกใช้ () {system.out.println ("-งานหนึ่ง ---"); ลอง {thread.sleep (1,000); RuntimeException ("ข้อผิดพลาด"); // เพิ่มภารกิจ 2, TALY EXECUTION TIMER.SCHEDULE (TIMERTASK ใหม่ () {@Override โมฆะสาธารณะเรียกใช้ () {สำหรับ (;;) {system.out.println ("-สองงาน ---"); ลอง {thread.sleep (1000);} catch (interruptedexception e) }}, 1,000); -ดังที่ได้กล่าวไว้ข้างต้นงานจะถูกเพิ่มครั้งแรกเพื่อดำเนินการหลังจาก 500ms จากนั้นงานที่สองจะถูกเพิ่มลงในการดำเนินการหลังจาก 1 วินาที สิ่งที่เราคาดหวังคือเมื่องานแรกส่งออก --- งานหนึ่ง --- และรอ 1s งานที่สองจะส่งออก-งานสองงาน --- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,- ,-
อย่างไรก็ตามหลังจากดำเนินการรหัสแล้วเอาต์พุตมีดังนี้:
ตัวอย่างที่ 2,
SHEDULE ชั้นเรียนสาธารณะ {เริ่มต้นแบบคงที่ส่วนตัว โมฆะคงที่สาธารณะหลัก (สตริง [] args) {timertask task = new timertask () {โมฆะสาธารณะเรียกใช้ () {system.out.println (System.currentTimeMillis ()-เริ่มต้น); ลอง {thread.sleep (3000); } catch (interruptedException e) {e.printStackTrace (); - timertask task1 = ใหม่ timertask () {@Override โมฆะสาธารณะเรียกใช้ () {system.out.println (System.currentTimeMillis ()-เริ่มต้น); - ตัวจับเวลาตัวจับเวลา = ตัวจับเวลาใหม่ (); start = system.currentTimeMillis (); // เริ่มงานที่กำหนดเวลาดำเนินการ timer.schedule (งาน, 1,000); // เริ่มงานที่กำหนดเวลาดำเนินการ Timer.Schedule (TASK1,3000); -เราคาดหวังว่าโปรแกรมข้างต้นจะถูกดำเนินการหลังจากงานแรกจะถูกดำเนินการหลังจากงานที่สองคือ 3s นั่นคือเอาท์พุทหนึ่ง 1,000 และหนึ่ง 3000
ผลการดำเนินงานจริงมีดังนี้:
ผลการดำเนินงานจริงไม่ได้เป็นอย่างที่เราต้องการ ผลลัพธ์ของโลกคืองานที่สองคือเอาต์พุตหลังจาก 4S นั่นคือ 4001 คือประมาณ 4 วินาที เวลานั้นไปที่ไหน? เวลานั้นถูกครอบครองโดยการนอนหลับครั้งแรกของเรา
ตอนนี้เราลบ thread.sleep () ในงานแรก บรรทัดของรหัสนี้ทำงานได้อย่างถูกต้องหรือไม่? ผลการดำเนินการมีดังนี้:
จะเห็นได้ว่างานแรกจะถูกดำเนินการหลังจาก 1s และงานที่สองจะถูกดำเนินการหลังจาก 3s หลังจากงานแรกถูกดำเนินการ
ซึ่งหมายความว่าตัวจับเวลาจะสร้างเธรดที่ไม่ซ้ำกันเพื่อดำเนินงานตัวจับเวลาทั้งหมด หากการดำเนินงานของตัวจับเวลาใช้เวลานานมันจะทำให้เกิดปัญหากับความถูกต้องของ timertasks อื่น ๆ
การวิเคราะห์หลักการใช้งานตัวจับเวลา
ต่อไปนี้เป็นการแนะนำสั้น ๆ เกี่ยวกับหลักการของตัวจับเวลา รูปต่อไปนี้เป็นการแนะนำรูปแบบหลักของตัวจับเวลา:
1.TaskQueue เป็นคิวลำดับความสำคัญที่นำมาใช้โดยกองทรีไบนารีที่สมดุลและวัตถุจับเวลาแต่ละชิ้นมีคิวงานที่ไม่ซ้ำกันภายใน วิธีกำหนดเวลาของตัวจับเวลาการเรียกเธรดผู้ใช้คือการเพิ่มงาน TimerTask ในคิวงาน เมื่อเรียกใช้วิธีกำหนดเวลาพารามิเตอร์การหน่วงเวลายาวจะถูกใช้เพื่อระบุระยะเวลาที่งานล่าช้าในการดำเนินการ
2. TimerThread เป็นเธรดที่ทำงานเฉพาะ มันได้รับงานที่มีลำดับความสำคัญน้อยที่สุดจากคิว Taskqueue สำหรับการดำเนินการ ควรสังเกตว่าหลังจากงานปัจจุบันถูกดำเนินการจะได้รับงานต่อไปจากคิว ไม่ว่าจะมีเวลาหน่วงเวลาในคิวตัวจับเวลามีเธรด timerthread เพียงหนึ่งเดียวเท่านั้นดังนั้นจะเห็นได้ว่าการใช้งานภายในของตัวจับเวลาเป็นแบบจำลองผู้ผลิตเดี่ยวแบบหลายผู้ผลิต
จากรูปแบบการใช้งานเราสามารถรู้ได้ว่าในการสำรวจปัญหาข้างต้นคุณจะต้องดูการใช้งานของ TimerThread ซอร์สโค้ดโลจิคัลหลักของวิธีการเรียกใช้ของ TimerThread มีดังนี้:
Public Void Run () {ลอง {mainloop (); } ในที่สุด {// มีคนฆ่าเธรดนี้ทำหน้าที่ราวกับว่าตัวจับเวลาได้ยกเลิกการซิงโครไนซ์ (คิว) {newtasksmaybescheduled = false; queue.clear (); // กำจัดการอ้างอิงที่ล้าสมัย}}} โมฆะส่วนตัว mainloop () {ในขณะที่ (จริง) {ลอง {timertask task; Boolean Taskfired; // ล็อคซิงโครไนซ์ (คิว) {...... } if (taskfired) task.run (); // execute task} catch (interruptedException e) {}}}}จะเห็นได้ว่าเมื่อข้อยกเว้นนอกเหนือจากการขัดจังหวะการรับรู้ถูกโยนลงระหว่างการดำเนินการงานเธรดผู้บริโภคเพียงอย่างเดียวจะยุติลงเนื่องจากการโยนข้อยกเว้นและงานอื่น ๆ ที่จะดำเนินการในคิวจะถูกล้าง ดังนั้นจึงเป็นการดีที่สุดที่จะใช้โครงสร้างการทดสอบเพื่อจับข้อยกเว้นหลักที่เป็นไปได้ในวิธีการเรียกใช้ของ TimerTask และอย่าโยนข้อยกเว้นนอกวิธีการเรียกใช้
ในความเป็นจริงการใช้ฟังก์ชั่นที่คล้ายกับตัวจับเวลาเป็นตัวเลือกที่ดีกว่าในการใช้กำหนดการของ ScheduleDTHREADPoolexecutor งานหนึ่งใน scheduledThreadPoolexecutor โยนข้อยกเว้นและงานอื่น ๆ จะไม่ได้รับผลกระทบ
ตัวอย่าง scheduledThreadPoolexecutor มีดังนี้:
/*** สร้างโดย Cong เมื่อปี 2018/7/12 */คลาสสาธารณะ ScheduleDTHREADPOOLEXECUTORTEST {Static ScheduleDTHREADPOOLExecutor ScheduleDTHreadPoolexecutor = ใหม่ ScheduleDTHREADPOOLEXECUTOR (1); โมฆะคงที่สาธารณะหลัก (สตริง [] args) {scheduledThreadpoolexecutor.schedule (ใหม่ runnable () {โมฆะสาธารณะเรียกใช้ () {system.out.println ("-งานหนึ่ง ---"); ลอง {thread.sleep (1000); }}, 500, TimeUnit.microseconds); scheduledThreadPoolexecutor.schedule (ใหม่ runnable () {public void run () {สำหรับ (int i = 0; i <5; ++ i) {system.out.println ("-สองภารกิจ-"); ลอง {thread.sleep (1000); Timeunit.microseconds); ScheduledThreadPoolexecutor.shutdown (); -ผลการดำเนินการมีดังนี้:
เหตุผลที่งานอื่น ๆ ของ scheduledThreadPoolexecutor ไม่ได้รับผลกระทบจากงานที่โยนข้อยกเว้นเป็นเพราะการจับลดลงในข้อยกเว้นในงาน ScheduledFutureTask ใน ScheduledThreadPoolexecutor แต่เป็นแนวปฏิบัติที่ดีที่สุด