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

Java 線程安全終止完整指南:interrupt、volatile 旗標與阻塞處理

     編輯:Java 程式設計入門

為什麼 stop()/suspend()/resume() 被棄用

三種正確終止策略對比

策略適用場景優點缺點阻塞處理
Run 自然結束短任務、有限迴圈最簡單、最安全無法外部控制不適用
volatile 旗標輪詢非阻塞計算、主動輪詢迴圈實現簡單、響應快阻塞時失效 (sleep/wait/IO)需配合 interrupt
interrupt() 中斷阻塞操作 (sleep/wait/join/IO/鎖)能打破阻塞、標準協議需處理 InterruptedException核心機制

方案一:volatile 旗標 + interrupt 混合模式 (推薦通用)


public class StoppableTask implements Runnable {
    // volatile 保證可見性:寫入立即對其他線程可見
    private volatile boolean running = true;

    @Override
    public void run() {
        try {
            while (running && !Thread.currentThread().isInterrupted()) {
                // 1. 正常業務邏輯
                doWork();

                // 2. 可選:主動檢查中斷狀態 (非阻塞點)
                if (Thread.interrupted()) { // 清除中斷狀態並返回舊值
                    break;
                }
            }
        } catch (InterruptedException e) {
            // 3. 阻塞方法拋出 InterruptedException
            Thread.currentThread().interrupt(); // 重置中斷狀態 (重要!)
        } finally {
            cleanup(); // 釋放資源、關閉連線
        }
    }

    public void stop() {
        running = false;      // 設置旗標
        // 如果線程可能阻塞,必須 interrupt
        // 需持有線程引用:thread.interrupt();
    }

    private void doWork() throws InterruptedException {
        // 模擬工作:計算 + 短暫睡眠
        // 睡眠會響應 interrupt
        Thread.sleep(100); 
    }

    private void cleanup() {
        // 關閉流、釋放鎖、歸還連線池
    }
}

方案二:標準中斷模式 (阻塞場景專用)


public class InterruptibleTask implements Runnable {
    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 業務邏輯
                processItem(fetchNextItem());

                // 定期主動檢查 (長時間計算時)
                // if (Thread.interrupted()) throw new InterruptedException();
            }
        } catch (InterruptedException e) {
            // 正常退出路徑:重置中斷狀態讓上層感知
            Thread.currentThread().interrupt(); 
        } catch (Exception e) {
            // 異常處理
            log.error("任務異常", e);
        }
    }

    private Object fetchNextItem() throws InterruptedException {
        // 阻塞隊列 take() 會響應中斷
        return blockingQueue.take(); 
    }
}

// 使用方式
Thread worker = new Thread(new InterruptibleTask());
worker.start();
// ... 稍後需要停止
worker.interrupt(); // 唯一標準方式

阻塞場景中斷響應對照表

阻塞方法響應 interrupt異常/行為備註
Thread.sleep()✅InterruptedException清除中斷狀態
Object.wait()✅InterruptedException清除中斷狀態
Thread.join()✅InterruptedException清除中斷狀態
BlockingQueue.take/put✅InterruptedException清除中斷狀態
ReentrantLock.lockInterruptibly()✅InterruptedException標準鎖不響應
Selector.select()✅返回 0, 中斷狀態保留NIO 非阻塞 IO
InputStream.read()❌ (傳統 IO)不拋異常需關閉 Socket/Stream 強制釋放
Channel.read()✅ (NIO)ClosedByInterruptExceptionAsynchronousChannel 異步關閉
Thread.sleep(0)✅InterruptedException可用於主動讓出 CPU 並檢查

處理不可中斷阻塞 (傳統 IO、鎖等待)


public class UninterruptibleTask implements Runnable {
    private volatile Thread runner;
    private final Socket socket;

    @Override
    public void run() {
        runner = Thread.currentThread();
        try (InputStream in = socket.getInputStream()) {
            byte[] buf = new byte[4096];
            while (!Thread.currentThread().isInterrupted()) {
                // read() 不響應 interrupt
                int len = in.read(buf); 
                if (len == -1) break;
                process(buf, len);
            }
        } catch (IOException e) {
            // Socket 關閉會導致 read 拋出 IOException
            if (!Thread.currentThread().isInterrupted()) {
                log.error("讀取異常", e);
            }
        }
    }

    public void stop() {
        // 1. 設置旗標 (邏輯層)
        // 2. 強制關閉底層資源 (物理層打破阻塞)
        try {
            socket.close(); // 導致 read() 拋出 SocketException
        } catch (IOException e) {
            log.warn("關閉 Socket 失敗", e);
        }
        // 3. 輔助 interrupt (雙重保險)
        if (runner != null) runner.interrupt();
    }
}

ExecutorService 优雅關閉模式


ExecutorService executor = Executors.newFixedThreadPool(10);

// 提交任務
Future future = executor.submit(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        doWork();
    }
});

// 關閉流程
public void shutdownGracefully(ExecutorService executor, long timeout, TimeUnit unit) {
    // 1. 拒絕新任務
    executor.shutdown(); 
    
    try {
        // 2. 等待現有任務完成
        if (!executor.awaitTermination(timeout, unit)) {
            // 3. 強制中斷執行中任務
            List remaining = executor.shutdownNow(); // 返回未執行任務
            log.warn("強制關閉,{} 個任務未完成", remaining.size());
        }
    } catch (InterruptedException e) {
        // 當前線程被中斷
        executor.shutdownNow();
        Thread.currentThread().interrupt();
    }
}

最佳實踐總結

  1. 永遠不使用 stop()、suspend()、resume()
  2. 首選 interrupt() 作為線程間取消信號
  3. 捕獲 InterruptedException 後:要么重置中斷狀態 Thread.currentThread().interrupt(),要么拋出/轉換為自訂異常
  4. 不要吞沒中斷:catch (InterruptedException e) { } 是嚴重錯誤
  5. volatile boolean 只能輔助非阻塞點檢查
  6. 阻塞 IO 使用 NIO (Channels/Selectors) 獲得原生中斷支援
  7. 資源清理放在 finally:確保鎖、連線、流正確釋放
  8. 測試中斷場景:單元測試驗證 interrupt() 能正確停止任務
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved