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

Java 線程中斷機制與 LockSupport 深度解析

     編輯:Java 程式設計入門

前言

在 Java 併發編程中,Thread.interrupt() 與 LockSupport 是兩個核心卻常被誤解的機制。本文將通過一系列問題引導,系統梳理線程中斷的原理、最佳實踐,以及 LockSupport.park()/unpark() 與傳統 Object.wait()/notify() 的異同,並附帶 JDK 源碼級分析與實戰代碼示例。

核心問題清單

  1. Thread.interrupt() 與 InterruptedException 的關係?是否由 interrupt 直接拋出異常?
  2. Thread.interrupt() 會中斷線程的哪種狀態?RUNNING 還是 BLOCKING?
  3. 日常開發是否需要關注中斷?如何處理?可用於什麼場景?
  4. LockSupport.park()/unpark() 與 Object.wait()/notify() 有何區別?
  5. LockSupport.park(Object blocker) 的 blocker 參數有何用途?
  6. LockSupport 能響應 Thread.interrupt() 嗎?會拋出 InterruptedException 嗎?
  7. 中斷處理是否有回調/鉤子函數?

Thread 中斷處理的三個關鍵方法

  • public void interrupt():觸發中斷事件
  • public boolean isInterrupted():檢查當前線程中斷狀態
  • public static boolean interrupted():檢查並清除當前線程中斷狀態(相當於 resetAndGet)

核心理解

  1. 每個線程擁有一個 interrupt status 標志位,標示是否處於中斷狀態。
  2. 調用 Thread.interrupt() 時有兩種處理路徑:
    • 若線程處於 Object.wait()、Thread.sleep()、Object.join() 等「低優先級阻塞」狀態,會立即解除阻塞並拋出 InterruptedException。
    • 其他情況下,僅設置中斷標志位,需業務代碼主動通過 isInterrupted() 檢查並決定後續行為(拋出異常、清理資源、取消任務等)。

最佳實踐(參考 IBM 文章)

1. 不要吞噬中斷

捕獲 InterruptedException 後,必須採取以下兩種方式之一:

  • 繼續拋出 InterruptedException。
  • 恢復中斷標志位:Thread.currentThread().interrupt(),讓上層調用者感知。

代碼示例:任務執行器

public class TaskRunner implements Runnable {
    private final BlockingQueue<Task> queue;
    public TaskRunner(BlockingQueue<Task> queue) { this.queue = queue; }
    public void run() {
        try {
            while (true) {
                Task task = queue.take(10, TimeUnit.SECONDS);
                task.execute();
            }
        } catch (InterruptedException e) {
            // 恢復中斷狀態
            Thread.currentThread().interrupt();
        }
    }
}

2. 利用中斷實現可取消任務

public class PrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;
    PrimeProducer(BlockingQueue<BigInteger> queue) { this.queue = queue; }
    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted())
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
            // 允許線程退出
        }
    }
    public void cancel() { interrupt(); }
}

註冊中斷處理鉤子(非標準用法)

常規設計採用主動輪詢 Thread.isInterrupted(),但存在嵌入性與延遲問題(如 socket.read 陷入時無法及時檢查)。借鑒 InterruptibleChannel 設計,可通過 sun.misc.SharedSecrets 註冊鉤子,實現中斷時的即時回調。

核心類結構

interface InterruptAble {
    void interrupt() throws InterruptedException;
}

abstract class InterruptSupport implements InterruptAble {
    private volatile boolean interrupted = false;
    private Interruptible interruptor = new Interruptible() {
        public void interrupt() {
            interrupted = true;
            InterruptSupport.this.interrupt(); // 位置3:回調子類實現
        }
    };

    public final boolean execute() throws InterruptedException {
        try {
            blockedOn(interruptor); // 位置1:綁定鉤子
            if (Thread.currentThread().isInterrupted()) {
                interruptor.interrupt();
            }
            bussiness(); // 業務邏輯
        } finally {
            blockedOn(null); // 位置2:清理鉤子,防止線程池複用污染
        }
        return interrupted;
    }

    protected abstract void bussiness();
    public abstract void interrupt();

    static void blockedOn(Interruptible intr) {
        sun.misc.SharedSecrets.getJavaLangAccess().blockedOn(Thread.currentThread(), intr);
    }
}

使用示例:可中斷的文件讀取

class InterruptRead extends InterruptSupport {
    private FileInputStream in;
    @Override public void bussiness() {
        File file = new File("/dev/urandom");
        try {
            in = new FileInputStream(file);
            byte[] bytes = new byte[1024];
            while (in.read(bytes, 0, 1024) > 0) {
                // 無需手動檢查中斷,鉤子會自動觸發
            }
        } catch (Exception e) { throw new RuntimeException(e); }
    }
    public FileInputStream getIn() { return in; }
    @Override public void interrupt() {
        try { in.getChannel().close(); } catch (IOException e) { e.printStackTrace(); }
    }
}

public static void main(String[] args) throws Exception {
    final InterruptRead test = new InterruptRead();
    Thread t = new Thread(() -> {
        long start = System.currentTimeMillis();
        try {
            System.out.println("InterruptRead start!");
            test.execute();
        } catch (InterruptedException e) {
            System.out.println("InterruptRead end! cost: " + (System.currentTimeMillis() - start));
            e.printStackTrace();
        }
    });
    t.start();
    Thread.sleep(3000);
    t.interrupt(); // 觸發中斷,鉤子關閉 Channel,read 拋出異常
}

JDK 源碼剖析

1. SharedSecrets 接入點

sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess() {
    public void blockedOn(Thread t, Interruptible b) { t.blockedOn(b); }
    // ... 其他方法省略
});

2. Thread.interrupt() 實現

public void interrupt() {
    if (this != Thread.currentThread()) checkAccess();
    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0(); // 設置中斷標志
            b.interrupt(); // 回調鉤子
            return;
        }
    }
    interrupt0();
}

LockSupport 深度解析

park()/unpark() 與 wait()/notify() 區別

維度LockSupportObject.wait()/notify()
面向主體線程級,可指定喚醒具體線程對象級,隨機喚醒或喚醒所有
阻塞隊列獨立於對象監視器,與 wait/notify 隔離基於對象監視器
靈活性無需持有鎖,可先 unpark 後 park必須在 synchronized 塊中調用

blocker 參數作用

LockSupport.park(Object blocker) 會將 blocker 存入線程的 parkBlocker 字段,便於 jstack 等工具診斷阻塞原因。

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    unsafe.park(false, 0L);
    setBlocker(t, null);
}

LockSupport 對中斷的響應

  • 能響應中斷:park() 會因中斷而返回。
  • 不拋出 InterruptedException:需自行檢查 Thread.interrupted()。

綜合測試代碼

文中提供了完整的 LockSupportTest 類,驗證以下場景:

  • lockSupportTest:park 後通過反射獲取 blocker,嘗試用 notifyAll 喚醒(失敗,證明隊列隔離)。
  • interruptWaitTest / interruptSleepTest:中斷 wait/sleep 拋出異常。
  • interruptParkTest:中斷 park 僅返回,不拋異常。
  • parkTest:unpark 喚醒後再中斷。

問題彙總答案

  1. interrupt 與 InterruptedException:僅在 wait/sleep/join 等少數阻塞方法中,interrupt 會導致拋出異常;其他情況僅設置標志位。
  2. 中斷針對狀態:設計初衷是處理 BLOCKING 狀態,但配合標志位檢查也可支持 RUNNING 狀態的任務取消。
  3. 開發關注點:用於解除阻塞、任務取消、資源清理。
  4. LockSupport vs Object:主體不同、隊列隔離、LockSupport 更靈活。
  5. blocker 用途:診斷輔助,jstack 可見。
  6. LockSupport 中斷行為:響應但不拋異常。
  7. 中斷回調:標準 API 無,但可通過 SharedSecrets 註冊 Interruptible 鉤子實現(屬高級/內部用法)。

結語

線程中斷是一種協作機制,而非強制終止。正確理解 interrupt()、InterruptedException、中斷標志位的交互關係,並掌握 LockSupport 的特性,能編寫出更健壯、可取消的併發程序。文中涉及的鉤子機制雖強大,但依賴 sun.misc 內部 API,生產環境謹慎使用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved