在 Java 併發編程中,Thread.interrupt() 與 LockSupport 是兩個核心卻常被誤解的機制。本文將通過一系列問題引導,系統梳理線程中斷的原理、最佳實踐,以及 LockSupport.park()/unpark() 與傳統 Object.wait()/notify() 的異同,並附帶 JDK 源碼級分析與實戰代碼示例。
Thread.interrupt() 與 InterruptedException 的關係?是否由 interrupt 直接拋出異常?Thread.interrupt() 會中斷線程的哪種狀態?RUNNING 還是 BLOCKING?LockSupport.park()/unpark() 與 Object.wait()/notify() 有何區別?LockSupport.park(Object blocker) 的 blocker 參數有何用途?LockSupport 能響應 Thread.interrupt() 嗎?會拋出 InterruptedException 嗎?public void interrupt():觸發中斷事件public boolean isInterrupted():檢查當前線程中斷狀態public static boolean interrupted():檢查並清除當前線程中斷狀態(相當於 resetAndGet)interrupt status 標志位,標示是否處於中斷狀態。Thread.interrupt() 時有兩種處理路徑:
Object.wait()、Thread.sleep()、Object.join() 等「低優先級阻塞」狀態,會立即解除阻塞並拋出 InterruptedException。isInterrupted() 檢查並決定後續行為(拋出異常、清理資源、取消任務等)。捕獲 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();
}
}
}
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 拋出異常
}
sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess() {
public void blockedOn(Thread t, Interruptible b) { t.blockedOn(b); }
// ... 其他方法省略
});
public void interrupt() {
if (this != Thread.currentThread()) checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 設置中斷標志
b.interrupt(); // 回調鉤子
return;
}
}
interrupt0();
}
| 維度 | LockSupport | Object.wait()/notify() |
|---|---|---|
| 面向主體 | 線程級,可指定喚醒具體線程 | 對象級,隨機喚醒或喚醒所有 |
| 阻塞隊列 | 獨立於對象監視器,與 wait/notify 隔離 | 基於對象監視器 |
| 靈活性 | 無需持有鎖,可先 unpark 後 park | 必須在 synchronized 塊中調用 |
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);
}
park() 會因中斷而返回。InterruptedException:需自行檢查 Thread.interrupted()。文中提供了完整的 LockSupportTest 類,驗證以下場景:
lockSupportTest:park 後通過反射獲取 blocker,嘗試用 notifyAll 喚醒(失敗,證明隊列隔離)。interruptWaitTest / interruptSleepTest:中斷 wait/sleep 拋出異常。interruptParkTest:中斷 park 僅返回,不拋異常。parkTest:unpark 喚醒後再中斷。jstack 可見。SharedSecrets 註冊 Interruptible 鉤子實現(屬高級/內部用法)。線程中斷是一種協作機制,而非強制終止。正確理解 interrupt()、InterruptedException、中斷標志位的交互關係,並掌握 LockSupport 的特性,能編寫出更健壯、可取消的併發程序。文中涉及的鉤子機制雖強大,但依賴 sun.misc 內部 API,生產環境謹慎使用。