首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 線程同步與定時任務:wait/notify、DelayQueue、ScheduledThreadPoolExecutor 實戰

     編輯:Java 相關知識

一、wait/notify 協作模型

Object.wait()、notify()、notifyAll() 是 Java 線程間通信的基礎,必須在 synchronized 同步塊/方法中調用,否則拋出 IllegalMonitorStateException。

關鍵差異

方法釋放鎖喚醒方式
sleep()否時間到期自動
yield()否讓出 CPU,重新競爭
wait()是notify/notifyAll 或 timeout

經典模式:生產者-消費者 / 等待通知

public class Monitor {
    private boolean ready = false;
    
    public synchronized void waitForReady() throws InterruptedException {
        while (!ready) { // 必須用 while 防止虛假喚醒
            wait();
        }
        System.out.println("收到通知,繼續執行");
    }
    
    public synchronized void setReady() {
        ready = true;
        notifyAll(); // 喚醒所有等待線程
    }
}

模擬比賽:單線程喚醒多線程

public class Game implements Runnable {
    private final Set players = new HashSet<>();
    private boolean start = false;
    
    public synchronized void prepare(Athlete a) throws InterruptedException {
        System.out.println(a + " ready!");
        while (!start) wait(); // 釋放鎖,等待發令槍
        System.out.println(a + " go!");
    }
    
    public synchronized void go() { notifyAll(); }
    
    public void ready() {
        for (Athlete a : players) new Thread(a).start(); // 啟動所有選手
    }
    
    @Override public void run() {
        System.out.println("Ready... Ready... Ready...");
        ready();
        start = true;
        System.out.println("Go!");
        go();
    }
}

二、忙等待 vs wait/notify 改進

忙等待 消耗 CPU:

while (!flag) { /* 空循環 */ } // 佔用 CPU 核心

wait/notify 改進:釋放鎖,進入等待隊列,零 CPU 佔用:

synchronized (monitor) {
    while (!flag) monitor.wait();
}
// 另一線程:
synchronized (monitor) {
    flag = true;
    monitor.notify();
}

三、定時任務兩大方案

方案一:DelayQueue 延遲隊列

適用於一次性延遲任務,無界阻塞隊列,元素需實作 Delayed 介面。

public class DelayedTask implements Delayed, Runnable {
    private final long triggerTime; // 納秒時間戳
    private final int id;
    
    public DelayedTask(int delaySeconds) {
        this.triggerTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(delaySeconds);
        this.id = counter++;
    }
    
    @Override public long getDelay(TimeUnit unit) {
        return unit.convert(triggerTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    
    @Override public int compareTo(Delayed o) {
        return Long.compare(this.triggerTime, ((DelayedTask)o).triggerTime);
    }
    
    @Override public void run() { System.out.println("執行任務 " + this); }
}

// 使用
DelayQueue queue = new DelayQueue<>();
queue.put(new DelayedTask(5)); // 5 秒後執行
new Thread(() -> {
    while (true) queue.take().run(); // 取出並執行
}).start();

優點:任務邏輯封裝在元素內、支援任意延遲、自然排序。
缺點:需自行管理消費線程、不支援週期性任務。

方案二:ScheduledThreadPoolExecutor 官方調度器

JDK 1.5+ 標準方案,支援一次性延遲與固定頻率/固定延遲週期任務。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);

// 1. 一次性延遲 3 秒執行
scheduler.schedule(() -> System.out.println("一次性任務"), 3, TimeUnit.SECONDS);

// 2. 固定頻率:初始延遲 1s,之後每 2s 執行一次(執行時間不計入間隔)
scheduler.scheduleAtFixedRate(() -> System.out.println("固定頻率"), 1, 2, TimeUnit.SECONDS);

// 3. 固定延遲:上次執行結束後延遲 2s 再執行
scheduler.scheduleWithFixedDelay(() -> System.out.println("固定延遲"), 1, 2, TimeUnit.SECONDS);

// 關閉
scheduler.shutdown();

核心方法對比

方法場景特點
schedule(Runnable, delay, unit)一次性延遲返回 ScheduledFuture,可取消
scheduleAtFixedRate(task, initialDelay, period, unit)固定頻率嚴格時間間隔,任務執行時間長會並發執行
scheduleWithFixedDelay(task, initialDelay, delay, unit)固定延遲上次結束後延遲,適合耗時不固定任務

四、選型建議

  • 簡單延遲/週期任務:首選 ScheduledThreadPoolExecutor,標準、健壯、支援取消、異常處理。
  • 大量異構延遲任務、需優先級排序:DelayQueue + 自定義消費線程池。
  • 分散式調度、持久化、彈性擴縮容:Quartz、XXL-JOB、Elastic-Job 等專業調度框架。
  • 線程協作、條件等待:wait/notify 或更高層 Condition、CountDownLatch、CompletableFuture。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved