熟悉 JavaScript 的人習慣用 setTimeout 實現網頁定時監控。如何在 Java 服務端實現類似機制?一般想到線程,但鮮有親自實踐。趁工作間隙分享線程編程魔力。
實現所有控制邏輯:線程鎖保證單任務執行、線程變量、循環開關、下次執行時間、抽象方法獲取下次時間與執行具體工作、啟動/停止/激活控制。
package com.test;
public abstract class SchedThread {
protected static final long NEVER = Long.MAX_VALUE;
private final Object lock = new Object();
private Thread thread;
private boolean active = true;
private long nextTime;
protected abstract long getNextTime();
protected abstract void executeWork();
protected String getName() { return getClass().getName(); }
public void start() {
thread = new Thread(new Runnable() {
public void run() { runInternal(); }
}, getName());
thread.start();
}
public void stop() throws InterruptedException {
synchronized (lock) {
active = false;
lock.notify();
}
thread.join();
}
public void workAdded(long time) {
synchronized (lock) {
if (time < nextTime) lock.notify();
}
}
private void runInternal() {
for (;;) {
try {
synchronized (lock) {
nextTime = getNextTime();
long interval = nextTime - System.currentTimeMillis();
if (interval > 0) {
try { lock.wait(interval); }
catch (InterruptedException e) { /* ignore */ }
}
if (!active) break;
}
executeWork();
} catch (Throwable t) {
try { Thread.sleep(10000); }
catch (InterruptedException ie) { /* ignore */ }
}
}
}
}
package com.test;
public class MyDataGenerator extends SchedThread {
protected void executeWork() {
System.out.println("Execute work ...");
}
protected long getNextTime() {
return System.currentTimeMillis() + 2000L; // 每 2 秒執行一次
}
public static void main(String[] argv) {
MyDataGenerator generator = new MyDataGenerator();
generator.start();
}
}
runInternal() 定義調度骨架,getNextTime()、executeWork() 留給子類實現Throwable 並睡眠 10 秒,保證監控線程不因業務異常而死掉workAdded() 允許外部提前喚醒線程stop() 通過 active 標誌 + join() 確保線程安全退出生產環境推薦使用成熟調度框架:
scheduleAtFixedRate、scheduleWithFixedDelayTaskScheduler:聲明式、支援 Cron 表達式本框架適合輕量級、無依賴場景,或作為理解調度原理的教學範例。