最近是遇到一個裝置線上離線的判定問題,設計是每個多長時間(正常的定時任務)檢測一次裝置是否在前,當檢測到裡離線時,我們不能立馬判斷為離線,而是要在重試多測幾次,隻要一次成功就傳回判定為線上,多次都不成功側是離線,我這裡相當了用ScheduledThreadPoolExecutor來實作,如有不足還請提出。如下:
ScheduledThreadPoolExecutor的介紹:
ScheduledThreadPoolExecutor,它可另行安排在給定的延遲後運作指令,或者定期執行指令。需要多個輔助線程時,或者要求 ThreadPoolExecutor 具有額外的靈活性或功能時,此類要優于Timer。
ScheduledThreadPoolExecutor的使用詳解
當程式需要用到一個定時器處理問題的時候,并且需要處理的頻率是很快的,這就需要一個穩定的定時器來保證資料的長久進行。ScheduledThreadPoolExecutor這個類就是個很好的選擇。正常情況下,定時器我們都是用Timer和TimerTask這兩個類就能完成定時任務,并且設定延長時間和循環時間間隔。
ScheduledThreadPoolExecutor也能完成Timer一樣的定時任務,并且時間間隔更加準确。
誤差說明:
我在背景程式看看一下Timer執行程式是有可能延遲1、2毫秒,如果是1秒執行一次的任務,1分鐘有可能延遲60毫秒,一小時延遲3600毫秒,相當于3秒,實際使用者看不出什麼差別。 但是,如果我的程式需要每40毫秒就執行一次任務,如果還是有1、2毫秒的誤差,1秒鐘就有25毫秒的誤差,大概40秒就有1秒的誤差,十幾分鐘就有十幾秒的誤差,這對UI顯示來說是延遲非常嚴重的了。 而我用ScheduledThreadPoolExecutor來做40毫秒的間隔任務,一般十幾分鐘才有1秒多的誤差,這個還是能接受的。 這也是我用ScheduledThreadPoolExecutor這個類的原因。
使用Timer和TimerTask存在一些缺陷:
1.Timer隻建立了一個線程。當你的任務執行的時間超過設定的延時時間将會産生一些問題。
2.Timer建立的線程沒有處理異常,是以一旦抛出非受檢異常,該線程會立即終止。
JDK 5.0以後推薦使用ScheduledThreadPoolExecutor。該類屬于Executor Framework,它除了能處理異常外,還可以建立多個線程解決上面的問題
Timer和TimerTask的使用 :
這裡就不做過多的描述了,重點在ScheduledThreadPoolExecutor。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
log.e("time:");
}
}, 2000, 40);
//2000表示第一次執行任務延遲時間,40表示以後每隔多長時間執行一次run裡面的任務
ScheduledThreadPoolExecutor的使用:
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 裝置線上延時檢查檢查
*/
@Slf4j
public class DelayedCheckDeviceSchedule {
public static final Integer CONNECT_TIME_OUT = 10000;
//引用的業務層
private ITblBaseService baseService = SpringContextHolder.getBean(ITblBaseService .class);
private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = null;
/**
* 需要延時檢查的裝置狀态的裝置id集合
*/
public static Set<String> deviceSet = new HashSet<>();
/**
* 目前執行點
*/
private AtomicInteger currentAtomicInteger = new AtomicInteger(1);
/**
* 初始化任務
* @param delay 延遲幾秒執行
* @param checkCount 需要檢測的次數
* @param deviceId 裝置Id
* @param deviceType 裝置類型
* @return
*/
public boolean init(long delay, int checkCount, String deviceId, String deviceType) {
log.info("第一次初始化時間"+deviceId+":"+System.currentTimeMillis());
if (deviceSet.add(deviceId) && deviceConnectModel!=null) {
this.scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(4);
this.scheduledThreadPoolExecutor.schedule(new Runnable() {
@Override
public void run() {
executor(delay, checkCount, deviceId, deviceType);
}
}, delay, TimeUnit.SECONDS);
return true;
}
return false;
}
/**
* 執行體
*/
private void executor(long delay, int checkCount, String deviceId, String deviceType) {
log.info("第"+currentAtomicInteger.get()+"執行時間"+deviceId+":"+System.currentTimeMillis());
if (deviceSet.contains(deviceId) && currentAtomicInteger.get() < (checkCount+1)) {
//執行邏輯
//當滿足條件時,停止任務
if(currentAtomicInteger.get()==checkCount){
//需要處理的邏輯
//停止任務
this.scheduledThreadPoolExecutor.shutdownNow();
deviceSet.remove(deviceId);
}else {
//下一次執行
currentAtomicInteger.getAndIncrement();
this.scheduledThreadPoolExecutor.schedule(new Runnable() {
@Override
public void run() {
executor(delay, checkCount, deviceId, deviceType);
}
}, delay, TimeUnit.SECONDS);
}
} else {
this.scheduledThreadPoolExecutor.shutdownNow();
this.scheduledThreadPoolExecutor = null;
}
}
/**
* 停止檢測任務
* @param deviceId
* @return
*/
public static boolean stop(String deviceId) {
return deviceSet.remove(deviceId);
}
}
在需要周期性檢查的時候引入:
DelayedCheckDeviceSchedule delayedCheckDeviceSchedule = new DelayedCheckDeviceSchedule();
delayedCheckDeviceSchedule.init(10, 3, panModel.getDeviceId(), "pan");