每次要延遲執行時,很多人只想到 Thread.sleep()。但你知道嗎?有七種寫法可以選,每種各有優缺點。選對的話,程式真香,選錯的話,維護時會想揍自己。
方法 1:Thread.sleep(ms) - 最基本
沒啥好說的,最原始的方式:
1 2 3 4 5 6 7
| try { System.out.println("Before sleep"); Thread.sleep(2000); System.out.println("After sleep"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
優點: 簡單直接
缺點: 毫秒單位,容易打錯;InterruptedException 必須處理;不易讀
方法 2:TimeUnit.SECONDS.sleep(n) - 可讀性最好
用 TimeUnit 包裝,讓意圖更清楚:
1 2 3 4 5 6 7
| try { System.out.println("Before sleep"); TimeUnit.SECONDS.sleep(2); System.out.println("After sleep"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
TimeUnit 有多種單位:
1 2 3 4
| TimeUnit.MILLISECONDS.sleep(500); TimeUnit.SECONDS.sleep(2); TimeUnit.MINUTES.sleep(1); TimeUnit.HOURS.sleep(1);
|
優點: 可讀性超好,單位清楚,不易出錯
缺點: 還是要 catch InterruptedException
我的建議:一般專案就用這個,真香。
方法 3:ScheduledExecutorService - 用排程執行
適合定期或延遲執行的任務:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Runnable task = () -> System.out.println("Task executed");
ScheduledFuture<?> future = scheduler.schedule(task, 2, TimeUnit.SECONDS);
try { future.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
scheduler.shutdown();
|
定期執行(每 2 秒重複一次):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate( () -> System.out.println("Repeating task"), 1, 2, TimeUnit.SECONDS );
scheduler.shutdown(); scheduler.awaitTermination(10, TimeUnit.SECONDS);
|
優點: 功能強大,支援定期任務、Future 取值
缺點: 複雜度高,要管理 executor service 的生命週期
方法 4:CompletableFuture.delayedExecutor - Java 9+
用 CompletableFuture 的 delayedExecutor:
1 2 3 4 5 6 7 8
| CompletableFuture<Void> future = CompletableFuture .runAsync( () -> System.out.println("Task executed"), CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS) );
future.join();
|
或結合其他 CompletableFuture 操作:
1 2 3 4 5 6 7 8
| CompletableFuture .supplyAsync(() -> { System.out.println("Getting data"); return "result"; }) .delayedExecutor(2, TimeUnit.SECONDS) .thenAccept(result -> System.out.println("Received: " + result)) .join();
|
優點: 函式式風格,能鏈接其他操作
缺點: 需要 Java 9+,使用 delayedExecutor() 的語法有點彆扭
方法 5:Timer/TimerTask - 舊式但仍可用
這是 Java 1.3 時代的遺跡,但在舊專案還是會看到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Timer timer = new Timer();
timer.schedule(new TimerTask() { @Override public void run() { System.out.println("Task executed"); } }, 2000);
timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { System.out.println("Repeating task"); } }, 1000, 2000);
timer.cancel();
|
優點: 很多舊程式都在用,改不動就只能用這個
缺點: 已過時,Timer 是單執行緒的(有瓶頸),ScheduledExecutorService 是它的升級版
盡量別用,除非維護舊程式。
方法 6:LockSupport.parkNanos - 底層 API
這是 JDK 內部用的,效率高但難用:
1 2 3
| System.out.println("Before park"); LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2)); System.out.println("After park");
|
或毫秒版本:
1 2 3
| long nanos = TimeUnit.MILLISECONDS.toNanos(2000); LockSupport.parkNanos(nanos);
|
何時用? 通常你用不到,除非寫高性能的併發框架(如 Disruptor)。
優點: 效率最高,允許被中斷
缺點: 低層 API,難理解,容易用錯
方法 7:Object.wait(timeout) - 配合 synchronized
用 wait 搭配 notify/notifyAll,適合執行緒間通信:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class WaitExample { private Object lock = new Object(); public void waitForNotification() throws InterruptedException { synchronized(lock) { System.out.println("Waiting..."); lock.wait(2000); System.out.println("Wait complete"); } } public void sendNotification() { synchronized(lock) { lock.notifyAll(); } } }
WaitExample example = new WaitExample();
Thread t1 = new Thread(() -> { try { example.waitForNotification(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } });
t1.start();
Thread.sleep(1000); example.sendNotification(); t1.join();
|
優點: 能於時間內被提前喚醒(非被動等待)
缺點: 複雜,容易踩坑(虛假喚醒 spurious wakeup)
七種方法的對比表
| 方法 |
單位清晰 |
易讀 |
功能強 |
複雜度 |
推薦度 |
| Thread.sleep() |
差 |
差 |
弱 |
低 |
⭐⭐ |
| TimeUnit.sleep() |
優 |
優 |
弱 |
低 |
⭐⭐⭐⭐⭐ |
| ScheduledExecutorService |
優 |
優 |
強 |
中 |
⭐⭐⭐⭐ |
| CompletableFuture.delayedExecutor() |
優 |
優 |
強 |
中 |
⭐⭐⭐ |
| Timer/TimerTask |
差 |
差 |
中 |
中 |
⭐ |
| LockSupport.parkNanos() |
優 |
差 |
弱 |
高 |
⭐⭐ |
| Object.wait() |
優 |
差 |
強 |
高 |
⭐⭐⭐ |
實務建議
場景 1:簡單延遲(一次性)
1 2
| TimeUnit.SECONDS.sleep(2);
|
場景 2:定期任務
1 2 3
| ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(task, 0, 2, TimeUnit.SECONDS);
|
場景 3:執行緒間通信 + 超時
1 2 3 4
| synchronized(lock) { lock.wait(2000); }
|
場景 4:複雜的非同步流程(Java 9+)
1 2
| CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS)
|
場景 5:維護舊專案
常見踩坑
坑 1:InterruptedException 別吞掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { }
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
坑 2:Timer 的單執行緒限制
1 2 3 4 5
| Timer timer = new Timer();
timer.schedule(longRunningTask, 0); timer.schedule(quickTask, 100);
|
用 ScheduledExecutorService 的執行緒池代替。
坑 3:wait() 的虛假喚醒
1 2 3 4 5 6 7 8 9 10 11 12 13
| synchronized(lock) { if (condition) { lock.wait(); } }
synchronized(lock) { while (!condition) { lock.wait(); } }
|
最後的話
Thread.sleep() 這麼簡單的東西,其實背後藏著這麼多選項。大部分情況下,用 TimeUnit.SECONDS.sleep() 就夠了,簡潔又清楚。需要排程或執行緒通信時才考慮其他方案。選對工具,程式會感謝你。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️