Java多線程之線程中斷

取消任務(wù)的方式

Java中沒有提供任何機制來安全地終止線程,但是提供了中斷(Interruption)協(xié)作機制,能夠使一個線程終止另一個線程的當(dāng)前工作. 一般取消或停止某個任務(wù),很少采用立即停止,因為立即停止會使得共享數(shù)據(jù)結(jié)構(gòu)出于不一致的狀態(tài).這也是Thread.stop(),Thread.suspend()以及Thread.resume()不安全的原因而廢棄.

Java中有三種方式可以終止當(dāng)前運行的線程:

  • 設(shè)置某個"已請求取消(Cancellation Requested)"標(biāo)記,而任務(wù)將定期查看該標(biāo)記的協(xié)作機制來中斷線程.
  • 使用Thread.stop()強制終止線程,但是因為這個方法"解鎖"導(dǎo)致共享數(shù)據(jù)結(jié)構(gòu)處于不一致而不安全被廢棄.
  • 使用Interruption中斷機制.

使用中斷標(biāo)記來中斷線程.


    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();
        }
    }

設(shè)置標(biāo)記的中斷策略: PrimeGenerator使用一種簡單取消策略,客戶端代碼通過調(diào)研cancel來請求取消,PrimeGenerator在每次搜索素數(shù)時前先檢查是否存在取消請求,如果不存在就退出.

但是使用設(shè)置標(biāo)記的中斷策略有一問題: 如果任務(wù)調(diào)用調(diào)用阻塞的方法,比如BlockingQueue.put,那么可能任務(wù)永遠不會檢查取消標(biāo)記而不會結(jié)束.


    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)
                    //此處阻塞,可能永遠無法檢測到結(jié)束的標(biāo)記
                    queue.put(p = p.nextProbablePrime());
            } catch (InterruptedException consumed) {
            }
        }
    
        public void cancel() {
            cancelled = true;
        }
    }

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

使用中斷(Interruption)請求取消

  • Thread類中的中斷方法:
    • public void interrupt()

      請求中斷,設(shè)置中斷標(biāo)記,而并不是真正中斷一個正在運行的線程,只是發(fā)出了一個請求中斷的請求,由線程在合適的時候中斷自己.

    • public static native boolean interrupted();

      判斷線程是否中斷,會擦除中斷標(biāo)記(判斷的是當(dāng)前運行的線程),另外若調(diào)用Thread.interrupted()返回為true時,必須要處理,可以拋出中斷異?;蛘咴俅握{(diào)用interrupt()來恢復(fù)中斷.

    • public native boolean isInterrupted();

      判斷線程是否中斷,不會擦除中斷標(biāo)記

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


    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()調(diào)用后到底意味著什么?

    首先一個線程不應(yīng)該由其他線程來強制中斷或者停止,而應(yīng)該由線程自己停止,所以Thread.stop(),Thread.suspend(),Thread.resume()都已被廢棄.而{@link Thread#interrupt}作用其實不是中斷線程,而請求線程中斷.具體來說,當(dāng)調(diào)用interrupt()方法時:

    • 如果線程處于阻塞狀態(tài)時(例如處于sleep,wait,join等狀態(tài)時)那么線程將立即退出阻塞狀態(tài)而拋出InterruptedException異常.
    • 如果 線程處于正?;顒訝顟B(tài),那么會將線程的中斷標(biāo)記設(shè)置為true,僅此而已.被設(shè)置中斷標(biāo)記的線程將繼續(xù)運行而不受影響.

    interrupt()并不能真正的中斷線程,需要被調(diào)用的線程自己進行配合才行:

    • 在正常運行任務(wù)時,經(jīng)常檢查本線程的中斷標(biāo)志位,如果被設(shè)置了中斷標(biāo)志就自行停止線程。
    • 在調(diào)用阻塞方法時正確處理InterruptedException異常。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過寫-讀內(nèi)存中的公共狀態(tài)進行隱...
    澤毛閱讀 4,492評論 2 21
  • 下面是我自己收集整理的Java線程相關(guān)的面試題,可以用它來好好準(zhǔn)備面試。 參考文檔:-《Java核心技術(shù) 卷一》-...
    阿呆變Geek閱讀 15,120評論 14 507
  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,584評論 1 15
  • 一、多線程 說明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)。 NEW:這種情況指的是,通過 New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,846評論 0 44
  • 要不你先拿一套足貼試用。用得好再來多拿兩套,我依然給你三套RM180的優(yōu)惠價格。 要不直接拿“三套RM180”優(yōu)惠...
    ShuJiun閱讀 192評論 0 0

友情鏈接更多精彩內(nèi)容