天天看點

Java并發程式設計實戰系列7之取消與關閉

JAVA媒體提供任務機制來安全的終止線程。但是它提供了中斷(interruption),這是一種寫作機制,能夠使一個線程終止另外一個線程。

一般來說沒人希望立即終止,因為必要時總要先清理再終止。

開發一個應用能夠妥善處理失敗、關閉、取消等過程非常重要也有挑戰。

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%2371-%25E4%25BB%25BB%25E5%258A%25A1%25E5%258F%2596%25E6%25B6%2588 7.1 任務取消

一定不要使用Thread.stop和suspend這些機制。

一種協作機制就是“标記位”。例如使用volatile類型的field來儲存取消狀态。

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

           

1.1 中斷

下面的例子會出現死鎖,線程根本不會停止

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

           

interrupt 方法:中斷目标線程。isInterrupted:傳回目标線程的中斷狀态。靜态的 interrupted方法:清除目前線程的中斷狀态,并傳回它之前的值。大多數可中斷的阻塞方法會在入口處檢查中斷狀态

對中斷操作(調用interrupt)的正确了解

他并不會真正的中斷一個正在運作的線程,而隻是發出中斷請求,然後由線程在下一個合适的時候中斷自己。比如,wait、sleep、

join等方法,當他們收到中斷請求或開始執行時,發現某個已經被設定好的中斷狀态,則抛出異常interruptedException。

每個線程都有一個boolean類型的中斷狀态。當調用Thread.interrupt方法時,該值被設定為true,Thread.interruptted可以恢複中斷。

阻塞庫方法,例如sleep和wait、join都會檢查中斷,并且發現中斷則提前傳回,他們會清楚中斷狀态,并抛出InterruptedException。

但是對于其他方法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) {
            /* Allow thread to exit */
        }
    }

    public void cancel() {
        interrupt();
    }
}

           

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23712-%25E4%25B8%25AD%25E6%2596%25AD%25E7%25AD%2596%25E7%2595%25A5 7.1.2 中斷政策

發生了中斷,需要盡快退出執行流程,并把中斷資訊傳遞給調用者,進而使調用棧中的上層代碼可以采取進一步的操作。當然任務也可以不需要放棄所有操作,可以推遲進行中斷清楚,知道某個時機。

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23713-%25E5%2593%258D%25E5%25BA%2594%25E4%25B8%25AD%25E6%2596%25AD 7.1.3 響應中斷

  • 傳遞異常
  • 回複中斷狀态
public class NoncancelableTask {
    public Task getNextTask(BlockingQueue<Task> queue) {
        boolean interrupted = false;
        try {
            while (true) {
                try {
                    return queue.take();
                } catch (InterruptedException e) {
                    interrupted = true;
                    // fall through and retry
                }
            }
        } finally {
            if (interrupted)
                Thread.currentThread().interrupt();
        }
    }

    interface Task {
    }
}

           

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23716-%25E5%25A4%2584%25E7%2590%2586%25E4%25B8%258D%25E5%258F%25AF%25E4%25B8%25AD%25E6%2596%25AD%25E7%259A%2584%25E9%2598%25BB%25E5%25A1%259E 7.1.6 處理不可中斷的阻塞

例如Socket I/O或者内置鎖都不能響應中斷,這時候該如何做才能終止他們呢?可以通過重寫Thread.interrupt方法,例如加入close的邏輯。

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%2372-%25E5%2581%259C%25E6%25AD%25A2%25E5%259F%25BA%25E4%25BA%258E%25E7%25BA%25BF%25E7%25A8%258B%25E7%259A%2584%25E6%259C%258D%25E5%258A%25A1 7.2 停止基于線程的服務

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23721-%25E7%25A4%25BA%25E4%25BE%258B%25E6%2597%25A5%25E5%25BF%2597%25E6%259C%258D%25E5%258A%25A1 7.2.1 示例:日志服務

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23722-%25E5%2585%25B3%25E9%2597%25ADexecutorservice 7.2.2 關閉ExecutorService

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23723-poison-pill 7.2.3 Poison Pill

例如CloseEvent機制或者POISON對象,來做特殊的識别,進而讓程式自己處理停止操作,退出線程。

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%2373-%25E5%25A4%2584%25E7%2590%2586%25E9%259D%259E%25E6%25AD%25A3%25E5%25B8%25B8%25E7%259A%2584%25E7%25BA%25BF%25E7%25A8%258B%25E7%25BB%2588%25E6%25AD%25A2 7.3 處理非正常的線程終止

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%2374-jvm%25E5%2585%25B3%25E9%2597%25AD 7.4 JVM關閉

https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2Fneoremind%2Fcoddding%2Fblob%2Fmaster%2Fcodding%2Fsrc%2Fmain%2Fjava%2Fnet%2Fneoremind%2Fmycode%2Fconcurrent%2FJAVA_CONCURRENCY_IN_PRACTICE_NOTES.md%23%25E7%25AC%25AC8%25E7%25AB%25A0-%25E7%25BA%25BF%25E7%25A8%258B%25E6%25B1%25A0%25E7%259A%2584%25E4%25BD%25BF%25E7%2594%25A8 第8章 線程池的使用

一個很好的

ThreadPoolExecutor源碼分析文檔

ThreadPoolExecutor UML圖:

https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2Fc0809a89c8ec3367d66a4181e6ab39f380c8b2e8%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d6578656375746f72732d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67
Java并發程式設計實戰系列7之取消與關閉
https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2Fc0809a89c8ec3367d66a4181e6ab39f380c8b2e8%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d6578656375746f72732d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67 image https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2Fc0809a89c8ec3367d66a4181e6ab39f380c8b2e8%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d6578656375746f72732d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67 https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2F6a87675bedf83eb673abb2c90025ebc35e52a584%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d636f6c6c656374696f6e732d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67
Java并發程式設計實戰系列7之取消與關閉
https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2F6a87675bedf83eb673abb2c90025ebc35e52a584%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d636f6c6c656374696f6e732d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67 https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2F6a87675bedf83eb673abb2c90025ebc35e52a584%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d636f6c6c656374696f6e732d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67 https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2F2c7da5881649bfadbae9dec00dd5b4c45ffdeb74%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d6675747572652d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67
Java并發程式設計實戰系列7之取消與關閉
https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2F2c7da5881649bfadbae9dec00dd5b4c45ffdeb74%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d6675747572652d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67 https://link.jianshu.com?t=https%3A%2F%2Fcamo.githubusercontent.com%2F2c7da5881649bfadbae9dec00dd5b4c45ffdeb74%2F687474703a2f2f6e656f72656d696e642e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031362f30392f6a6176612d372d636f6e63757272656e742d6675747572652d756d6c2d636c6173732d6469616772616d2d6578616d706c652e706e67