原文地址
繼續併發,上篇博客對於ScheduledThreadPoolExecutor沒有進行介紹,說過會和Timer一直單獨寫一篇Blog.
1、Timer管理延時任務的缺陷
a、以前在項目中也經常使用定時器,比如每隔一段時間清理項目中的一些垃圾文件,每個一段時間進行數據清洗;然而Timer是存在一些缺陷的,因爲Timer在執行定時任務時只會創建一個線程,所以如果存在多個任務,且任務時間過長,超過了兩個任務的間隔時間,會發生一些缺陷:下面看例子:
Timer的源碼:
public class Timer {
/**
* The timer task queue. This data structure is shared with the timer
* thread. The timer produces tasks, via its various schedule calls,
* and the timer thread consumes, executing timer tasks as appropriate,
* and removing them from the queue when they're obsolete.
*/
private TaskQueue queue = new TaskQueue();
/**
* The timer thread.
*/
private TimerThread thread = new TimerThread(queue);
TimerThread是Thread的子類,可以看出內部只有一個線程。下面看個例子:
package com.zhy.concurrency.timer;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest
{
private static long start;
public static void main(String[] args) throws Exception
{
TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task1 invoked ! "
+ (System.currentTimeMillis() - start));
try
{
Thread.sleep(3000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked ! "
+ (System.currentTimeMillis() - start));
}
};
Timer timer = new Timer();
start = System.currentTimeMillis();
timer.schedule(task1, 1000);
timer.schedule(task2, 3000);
}
}
定義了兩個任務,預計是第一個任務1s後執行,第二個任務3s後執行,但是看運行結果
task1 invoked ! 1000
task2 invoked ! 4000
task2實際上是4後才執行,正因爲Timer內部是一個線程,而任務1所需的時間超過了兩個任務間的間隔導致。下面使用ScheduledThreadPool解決這個問題:
package com.zhy.concurrency.timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExecutorTest
{
private static long start;
public static void main(String[] args)
{
/**
* 使用工廠方法初始化一個ScheduledThreadPool
*/
ScheduledExecutorService newScheduledThreadPool = Executors
.newScheduledThreadPool(2);
TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
try
{
System.out.println("task1 invoked ! "
+ (System.currentTimeMillis() - start));
Thread.sleep(3000);
} catch (Exception e)
{
e.printStackTrace();
}
}
};
TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked ! "
+ (System.currentTimeMillis() - start));
}
};
start = System.currentTimeMillis();
newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
}
}
輸出結果:
task1 invoked ! 1001
task2 invoked ! 3001
符合我們的預期結果。因爲ScheduledThreadPool內部是個線程池,所以可以支持多個任務併發執行。
2、Timer當任務拋出異常時的缺陷
如果TimerTask拋出RuntimeException,Timer會停止所有任務的運行:
package com.zhy.concurrency.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ScheduledThreadPoolDemo01
{
public static void main(String[] args) throws InterruptedException
{
final TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
throw new RuntimeException();
}
};
final TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked!");
}
};
Timer timer = new Timer();
timer.schedule(task1, 100);
timer.scheduleAtFixedRate(task2, new Date(), 1000);
}
}
上面有兩個任務,任務1拋出一個運行時的異常,任務2週期性的執行某個操作,輸出結果:
task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
at com.zhy.concurrency.timer.ScheduledThreadPoolDemo01$1.run(ScheduledThreadPoolDemo01.java:24)
at java.util.TimerThread.mainLoop(Timer.java:512)
at java.util.TimerThread.run(Timer.java:462)
由於任務1的一次,任務2也停止運行了。。。下面使用ScheduledExecutorService解決這個問題:
package com.zhy.concurrency.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolDemo01
{
public static void main(String[] args) throws InterruptedException
{
final TimerTask task1 = new TimerTask()
{
@Override
public void run()
{
throw new RuntimeException();
}
};
final TimerTask task2 = new TimerTask()
{
@Override
public void run()
{
System.out.println("task2 invoked!");
}
};
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
pool.scheduleAtFixedRate(task2, 0 , 1000, TimeUnit.MILLISECONDS);
}
}
代碼基本一致,但是ScheduledExecutorService可以保證,task1出現異常時,不影響task2的運行:
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
task2 invoked!
3、Timer執行週期任務時依賴系統時間
Timer執行週期任務時依賴系統時間,如果當前系統時間發生變化會出現一些執行上的變化,ScheduledExecutorService基於時間的延遲,不會由於系統時間的改變發生執行變化。
上述,基本說明了在以後的開發中儘可能使用ScheduledExecutorService(JDK1.5以後)替代Timer。