一、ScheduledThreadPoolExecutor是什麼
ScheduledThreadPoolExecutor是一個多線程任務調度器,可以在指定的時間(延遲)後執行任務或定期執行任務。使用ScheduledThreadPoolExecutor可以幫助我們實現複雜的任務調度需求,如定時任務、延遲任務、周期性任務等。
ScheduledThreadPoolExecutor的主要屬性:
- corePoolSize: 線程池核心線程數,即同時執行的任務數。
- maximumPoolSize: 線程池最大線程數。
- keepAliveTime: 空閑線程的存活時間。
- ThreadFactory: 線程工廠,用於創建線程對象。
- RejectedExecutionHandler: 拒絕策略,用於決定當任務隊列已滿時如何處理新任務。
import java.util.concurrent.*;
public class ScheduledThreadPoolExample {
public static void main(String[] args) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> {
System.out.println("Task executed at " + System.currentTimeMillis());
};
System.out.println("Scheduled to run after 2 seconds");
executor.schedule(task, 2, TimeUnit.SECONDS);
System.out.println("Scheduled to run after every 3 seconds, with initial delay of 1 second");
executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
System.out.println("Shutdown executor");
executor.shutdown();
}
}
二、ScheduledThreadPoolExecutor的使用
1、延遲任務
我們可以使用schedule()方法在指定的時間後執行任務,這個時間可以用時間和時間單位(TimeUnit)表示。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> {
System.out.println("Task executed at " + System.currentTimeMillis());
};
System.out.println("Scheduled to run after 2 seconds");
executor.schedule(task, 2, TimeUnit.SECONDS);
上面的代碼中,我們創建了一個ScheduledExecutorService,然後通過schedule()方法延遲2秒執行任務。在任務執行時,我們會輸出任務執行時間的信息。
2、周期性任務
我們也可以使用scheduleAtFixedRate()方法定期執行任務,參數包括初始延遲時間、執行周期,以及時間單位。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Runnable task = () -> {
System.out.println("Task executed at " + System.currentTimeMillis());
};
System.out.println("Scheduled to run after every 3 seconds, with initial delay of 1 second");
executor.scheduleAtFixedRate(task, 1, 3, TimeUnit.SECONDS);
上面的代碼中,我們執行了一個周期性任務,並且設置了初始延遲時間為1秒,執行周期為3秒。在任務執行時,我們同樣會輸出任務執行時間的信息。
3、使用線程池
ScheduledThreadPoolExecutor本質上仍然是一個線程池,我們可以像使用ThreadPoolExecutor一樣使用ScheduledThreadPoolExecutor。
首先,我們需要創建一個ScheduledExecutorService:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
然後,我們可以像ThreadPoolExecutor一樣提交任務:
Runnable task = () -> {
System.out.println("Task executed at " + System.currentTimeMillis());
};
executor.submit(task);
當然,我們也可以使用schedule()或者scheduleAtFixedRate()方法提交任務,只需要把任務封裝為Runnable或者Callable介面即可。
4、異常處理
在任務執行過程中,如果任務拋出了異常,ScheduledThreadPoolExecutor將並不會列印異常堆棧信息,也不會將異常拋到調用方。因此,我們需要在任務中顯式地處理異常。
Runnable task = () -> {
try {
// ...
} catch (Exception e) {
e.printStackTrace();
}
};
三、ScheduledThreadPoolExecutor的拓展
1、線程池監控
使用ScheduledThreadPoolExecutor可以實現線程池的監控,比如通過定期列印線程池的狀態來觀察線程池的健康狀態。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
ScheduledFuture future = executor.scheduleAtFixedRate(() -> {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
System.out.println("Pool Size: " + threadPoolExecutor.getPoolSize());
System.out.println("Active Count: " + threadPoolExecutor.getActiveCount());
System.out.println("Completed Task Count: " + threadPoolExecutor.getCompletedTaskCount());
System.out.println("Task Count: " + threadPoolExecutor.getTaskCount());
}, 0, 1, TimeUnit.SECONDS);
// 在程序結束時停止監控線程池
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
future.cancel(true);
executor.shutdown();
}));
上面的代碼中,我們通過修改線程池的拒絕策略來列印線程池的狀態信息。在程序結束時,我們需要使用ShutdownHook來關閉線程池,確保程序結束時不會留下線程池中的未完成任務。
2、拒絕策略
當任務隊列已滿時,如果再提交新的任務,ScheduledThreadPoolExecutor默認的拒絕策略是拋出RejectedExecutionException異常。為了避免出現這種情況,我們可以自定義拒絕策略。
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (executor.isShutdown()) {
// 如果線程池已經關閉,則直接拒絕任務
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + executor.toString());
} else {
// 否則,將任務放回任務隊列,嘗試繼續執行。
executor.getQueue().offer(r);
}
}
}
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2, new CustomRejectedExecutionHandler());
上面的代碼中,我們實現了一個自定義的拒絕策略CustomRejectedExecutionHandler。當任務隊列已滿時,我們會首先檢測線程池是否已經關閉。如果線程池已經關閉,則直接拋出拒絕執行異常,否則將任務放回任務隊列,等待線程池的新線程來執行。
3、定時任務比較工具
在一些需要進行定期任務處理的項目中,需要精確地掌握每個任務的執行情況。定時任務比較工具可以對比實際執行時間與定時時間之間的誤差,這樣就可以幫助我們調整任務的定時精度,保證任務的準確執行。
public class ScheduleTimeComparator implements Comparable {
private final Long scheduleTime;
private final Long actualTime;
public ScheduleTimeComparator(Long scheduleTime, Long actualTime) {
this.scheduleTime = scheduleTime;
this.actualTime = actualTime;
}
public boolean isDelay() {
return actualTime - scheduleTime > 0;
}
public Long getDelay() {
return actualTime - scheduleTime;
}
@Override
public int compareTo(ScheduleTimeComparator o) {
return this.actualTime.compareTo(o.actualTime);
}
}
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
List list = new ArrayList();
Runnable task = () -> {
list.add(new ScheduleTimeComparator(System.currentTimeMillis() - 2000, System.currentTimeMillis()));
};
for (int i = 0; i < 10; i++) {
executor.schedule(task, i * 100, TimeUnit.MILLISECONDS);
}
Thread.sleep(5000);
list.sort(Comparator.comparing(ScheduleTimeComparator::getDelay).reversed());
for (int i = 0; i < list.size(); i++) {
ScheduleTimeComparator element = list.get(i);
System.out.println(String.format("Index=%d, Scheduled Time=%d, Actual Time=%d, Delay=%d, Is Delay=%b", i, element.scheduleTime, element.actualTime, element.getDelay(), element.isDelay()));
}
上面的代碼中,我們通過創建一個ScheduleTimeComparator類來記錄定時時間和實際執行時間之間的誤差。當所有任務執行完成後,我們對誤差進行排序並輸出。
四、總結
本文介紹了ScheduledThreadPoolExecutor的使用方法和拓展,包括定時任務、周期性任務、線程池監控、拒絕策略、定時任務比較工具等。在實現複雜任務調度的過程中,ScheduledThreadPoolExecutor是一個非常實用的工具。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/195410.html
微信掃一掃
支付寶掃一掃