import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TestScheduledThreadPoolExecutor { public static void main(String[] args) { String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.PRintln("Start time : " + time); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5); //創(chuàng)建5個執(zhí)行線程 Runnable runnable = new Runnable() { @Override public void run() { // TODO Auto-generated method stub String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("Now Time : " + time); } }; executor.scheduleWithFixedDelay(runnable, 2, 3, TimeUnit.SECONDS); } } 在實際應用中,有時候我們需要創(chuàng)建一些個延遲的、并具有周期性的任務,比如,我們希望當我們的程序啟動后每隔1小時就去做一次日志記錄。在JDK中提供了兩種方法去創(chuàng)建延遲周期性任務。TimerTimer是java.util包下的一個類,在JDK1.3的時候被引入,Timer只是充當了一個執(zhí)行者的角色,真正的任務邏輯是通過一個叫做TimerTask的抽象類完成的,TimerTask也是java.util包下面的類,它是一個實現(xiàn)了Runnable接口的抽象類,包含一個抽象方法run( )方法,需要我們自己去提供具體的業(yè)務實現(xiàn)。Timer類對象是通過其schedule方法執(zhí)行TimerTask對象中定義的業(yè)務邏輯,并且schedule方法擁有多個重載方法提供不同的延遲與周期性服務。下面是利用Timer去創(chuàng)建的一個延時周期性任務import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TestTimer { public static void main(String[] args) { String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("Start time : " + time); Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { // TODO Auto-generated method stub String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("Now Time : " + time); } }; //end task timer.schedule(task, 2000, 3000); } } 程序的輸出:Start time : 21:36:08 Now Time : 21:36:10 Now Time : 21:36:13 Now Time : 21:36:16 Now Time : 21:36:19 ScheduledThreadPoolExecutor在JDK1.5的時候在java.util.concurrent并發(fā)包下引入了ScheduledThreadPoolExecutor類,引入它的原因是因為Timer類創(chuàng)建的延遲周期性任務存在一些缺陷,ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor,并且實現(xiàn)了ScheduledExecutorService接口,ScheduledThreadPoolExecutor也是通過schedule方法執(zhí)行Runnable任務的。我們用ScheduledThreadPoolExecutor來實現(xiàn)和上述Timer一樣的功能程序的輸出:Start time : 22:12:25 Now Time : 22:12:27 Now Time : 22:12:30 Now Time : 22:12:33 Now Time : 22:12:36 這樣看來Timer和ScheduledThreadPoolExecutor好像沒有聲明差別,但是ScheduledThreadPoolExecutor的引入正是由于Timer類存在的一些不足,并且在JDK1.5或更高版本中,幾乎沒有利用繼續(xù)使用Timer類,下面說明Timer存在的一些缺點。單線程Timer類是通過單線程來執(zhí)行所有的TimerTask任務的,如果一個任務的執(zhí)行過程非常耗時,將會導致其他任務的時效性出現(xiàn)問題。而ScheduledThreadPoolExecutor是基于線程池的多線程執(zhí)行任務,不會存在這樣的問題。這里我們通過讓Timer來執(zhí)行兩個TimerTask任務來說明,其中一個TimerTask的執(zhí)行過程是耗時的,加入需要2秒。import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class SingleThreadTimer { public static void main(String[] args) { String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("Start time : " + time); Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @Override public void run() { // TODO Auto-generated method stub String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("Task1 time : " + time); } }; TimerTask task2 = new TimerTask() { @Override public void run() { // TODO Auto-generated method stub try { Thread.sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); System.out.println("task2 time : " + time); } }; timer.schedule(task1, 2000, 1000); timer.schedule(task2, 2000, 3000); } } 這里定義了兩個任務,任務1,程序啟動2秒后每隔1秒運行一次,任務2,程序啟動2秒后,每隔3秒運行1次,然后讓Timer同時運行這兩個任務程序的輸出如下:Start time : 22:22:37 Task1 time : 22:22:39 task2 time : 22:22:41 Task1 time : 22:22:41 Task1 time : 22:22:42 task2 time : 22:22:44 Task1 time : 22:22:44 Task1 time : 22:22:45 task2 time : 22:22:47 Task1 time : 22:22:47 Task1 time : 22:22:48 可以分析,無論是任務1還是任務2都沒有按照我們設(shè)定的預期進行運行,造成這個現(xiàn)象的原因就是Timer類是單線程的。Timer線程不捕獲異常Timer類中是不捕獲異常的,假如一個TimerTask中拋出未檢查異常(P.S:java中異常分為兩類:checked exception(檢查異常)和unchecked exception(未檢查異常),對于未檢查異常也叫RuntimeException(運行時異常). ),Timer類將不會處理這個異常而產(chǎn)生無法預料的錯誤。這樣一個任務拋出異常將會導致整個Timer中的任務都被取消,此時已安排但未執(zhí)行的TimerTask也永遠不會執(zhí)行了,新的任務也不能被調(diào)度(所謂的“線程泄漏”現(xiàn)象)。下面就已常見的RuntimeException,ArrayIndexOutOfBoundsException數(shù)組越界異常,來演示這個缺點:import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TestTimerTask { public static void main(String[] args) { System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date())); Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @Override public void run() { System.out.println("1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } }; TimerTask task2 = new TimerTask() { @Override public void run() { int[] arr = {1,2,3,4,5}; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int index = (int)(Math.random()*100); System.out.println(arr[index]); System.out.println("2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } }; timer.schedule(task1, 2000, 3000); timer.schedule(task2, 2000, 1000); } } 程序會在運行過程中拋出數(shù)組越界異常,并且整個程序都會被終止,原來完好的任務1也被終止了。基于絕對時間Timer類的調(diào)度是基于絕對的時間的,而不是相對的時間,因此Timer類對系統(tǒng)時鐘的變化是敏感的,舉個例子,加入你希望任務1每個10秒執(zhí)行一次,某個時刻,你將系統(tǒng)時間提前了6秒,那么任務1就會在4秒后執(zhí)行,而不是10秒后。在ScheduledThreadPoolExecutor,任務的調(diào)度是基于相對時間的,原因是它在任務的內(nèi)部存儲了該任務距離下次調(diào)度還需要的時間(使用的是基于System#nanoTime實現(xiàn)的相對時間,不會因為系統(tǒng)時間改變而改變,如距離下次執(zhí)行還有10秒,不會因為將系統(tǒng)時間調(diào)前6秒而變成4秒后執(zhí)行)。基于以上3個弊端,在JDK1.5或以上版本中,我們幾乎沒有理由繼續(xù)使用Timer類,ScheduledThreadPoolExecutor可以很好的去替代Timer類來完成延遲周期性任務。
新聞熱點
疑難解答