1. 程式人生 > >Java多執行緒之執行緒中斷

Java多執行緒之執行緒中斷

取消任務的方式

Java中沒有提供任何機制來安全地終止執行緒,但是提供了中斷(Interruption)協作機制,能夠使一個執行緒終止另一個執行緒的當前工作. 一般取消或停止某個任務,很少採用立即停止,因為立即停止會使得共享資料結構出於不一致的狀態.這也是Thread.stop(),Thread.suspend()以及Thread.resume()不安全的原因而廢棄.

Java中有三種方式可以終止當前執行的執行緒:

  • 設定某個"已請求取消(Cancellation Requested)"標記,而任務將定期檢視該標記的協作機制來中斷執行緒.
  • 使用Thread.stop()強制終止執行緒,但是因為這個方法”解鎖”導致共享資料結構處於不一致而不安全被廢棄.
  • 使用Interruption中斷機制.

使用中斷標記來中斷執行緒.


    public class PrimeGenerator implements Runnable {
        private static ExecutorService exec = Executors.newCachedThreadPool();

        @GuardedBy("this") private final List<BigInteger> primes
                = new ArrayList<BigInteger>();
        private
volatile boolean cancelled; public void run() { BigInteger p = BigInteger.ONE; while (!cancelled) { p = p.nextProbablePrime(); synchronized (this) { primes.add(p); } } } public
void cancel() { cancelled = true; } public synchronized List<BigInteger> get() { return new ArrayList<BigInteger>(primes); } static List<BigInteger> aSecondOfPrimes() throws InterruptedException { PrimeGenerator generator = new PrimeGenerator(); exec.execute(generator); try { SECONDS.sleep(1); } finally { generator.cancel(); } return generator.get(); } }

設定標記的中斷策略: PrimeGenerator使用一種簡單取消策略,客戶端程式碼通過調研cancel來請求取消,PrimeGenerator在每次搜尋素數時前先檢查是否存在取消請求,如果不存在就退出.

但是使用設定標記的中斷策略有一問題: 如果任務呼叫呼叫阻塞的方法,比如BlockingQueue.put,那麼可能任務永遠不會檢查取消標記而不會結束.


    class BrokenPrimeProducer extends Thread {
        private final BlockingQueue<BigInteger> queue;
        private volatile boolean cancelled = false;

        BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
            this.queue = queue;
        }

        public void run() {
            try {
                BigInteger p = BigInteger.ONE;
                while (!cancelled)
                    //此處阻塞,可能永遠無法檢測到結束的標記
                    queue.put(p = p.nextProbablePrime());
            } catch (InterruptedException consumed) {
            }
        }

        public void cancel() {
            cancelled = true;
        }
    }

解決辦法也很簡單: 使用中斷而不是使用boolean標記來請求取消

使用中斷(Interruption)請求取消

  • Thread類中的中斷方法:

    • public void interrupt()

      請求中斷,設定中斷標記,而並不是真正中斷一個正在執行的執行緒,只是發出了一個請求中斷的請求,由執行緒在合適的時候中斷自己.

    • public static native boolean interrupted();

      判斷執行緒是否中斷,會擦除中斷標記(判斷的是當前執行的執行緒),另外若呼叫Thread.interrupted()返回為true時,必須要處理,可以丟擲中斷異常或者再次呼叫interrupt()來恢復中斷.

    • public native boolean isInterrupted();

      判斷執行緒是否中斷,不會擦除中斷標記

故而上面問題的解決方案如下:


    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) {
                /* Allow thread to exit */
            }
        }

        public void cancel() {
            interrupt();
        }
    }
  • 那麼thread.interrupt()呼叫後到底意味著什麼?

    首先一個執行緒不應該由其他執行緒來強制中斷或者停止,而應該由執行緒自己停止,所以Thread.stop(),Thread.suspend(),Thread.resume()都已被廢棄.而{@link Thread#interrupt}作用其實不是中斷執行緒,而請求執行緒中斷.具體來說,當呼叫interrupt()方法時:

    • 如果執行緒處於阻塞狀態時(例如處於sleep,wait,join等狀態時)那麼執行緒將立即退出阻塞狀態而丟擲InterruptedException異常.
    • 如果 執行緒處於正常活動狀態,那麼會將執行緒的中斷標記設定為true,僅此而已.被設定中斷標記的執行緒將繼續執行而不受影響.

    interrupt()並不能真正的中斷執行緒,需要被呼叫的執行緒自己進行配合才行:

    • 在正常執行任務時,經常檢查本執行緒的中斷標誌位,如果被設定了中斷標誌就自行停止執行緒。
    • 在呼叫阻塞方法時正確處理InterruptedException異常。