1. 程式人生 > >java並發基礎(四)--- 取消與關閉

java並發基礎(四)--- 取消與關閉

rime ole out sys 類型 interrupt 來看 方法 發出

  《java並發編程實戰》的第7章是任務的取消與關閉。我覺得這一章和第6章任務執行同樣重要,一個在行為良好的軟件和勉強運行的軟件之間的最主要的區別就是,行為良好的軟件能很完善的處理失敗、關閉和取消等過程。

一、任務取消

  在java中沒有一種安全的搶占式(收到中斷請求就立刻停止)的方式來停止線程,因此也沒有安全的搶占式方法來停止任務。只有一些協作式的機制,比如設置請求已取消的標識。在下面的例子中,PrimeGenarator持續的列出素數,直到它被取消。cancel方法將設置cancelled標識,並且主循環在搜索下一個素數之前會檢查這個標識,為了使這個過程可靠工作,cancelled必須是volatile類型。

//使用volatile類型的域來保護取消狀態
public class PrimeGenerator implements Runnable{
    private final List<BigInteger> primes = new ArrayList<BigInteger>();
    
    private volatile boolean cancelled;
    
    @Override
    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); } }

測試,讓genarator只執行一秒。

public static void main(String[] args) throws Exception{
        PrimeGenerator generator = new PrimeGenerator();
        new Thread(generator).start();
        try {
            Thread.sleep(1000);
        }finally{
            generator.cancel();
        }
        
        System.out.println(generator.get());;
}

打印結果就不陳列了,無非就是一些素數而已。

二、中斷

  每一個線程都有一個boolean類型的中斷狀態。當中斷線程時,這個線程的中斷狀態將被設置為true ,在Thread中包含了中斷線程以及查詢線程中斷狀態的方法,如下:

public class Thread{
    //中斷目標線程(但線程不會立刻停止,也就是不會搶占式停止)
    public void interrupt(){}
    //返回線程的中斷狀態 已中斷:true 未中斷:false
    public boolean isInterrupted(){}
    //清除當前線程的中斷狀態,並返回它之前的值,這是清除中斷狀態的唯一方法
    public static boolean interrupted(){}
}

  如果一個線程被中斷,會發生兩件事情:1.清除中斷狀態 2.拋出InterruptedException異常,所以,有時候如果捕獲了InterruptedException後還要有其他操作的話,要把當前線程中斷:Thread.currentThread().interrupt();然後再做其他事。

  還有一點需要註意,調用interrupt並不意味著立即停止目標線程正在進行的工作,而只是傳遞了請求中斷的消息。對中斷操作的正確理解是:它並不會真正的中斷一個正在運行的線程,而只是發出中斷請求,然後由線程在下一個合適的時刻自己中斷。

  現在我們回過頭來看PrimeGenarator的例子,如果代碼中出現了阻塞隊列,那這種用volatile做標識的取消就可能會有問題,比如基於生產者和消費者模式的素數生成:

//生產者
class BrokenPrimeProducer extends Thread{
    
    private final BlockingQueue<BigInteger> queue;
    private volatile boolean cancelled = false;
    
    public BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }
    
    @Override
    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!cancelled) {
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException e) {
            
        }
    }
    
    public void cancel(){cancelled = true;}
}

  消費者的代碼就不寫了,無非是從阻塞隊列中取出素數。你看,這個時候,生產者線程生成素數,並將它們放入阻塞隊列中,如果生產者的速度超過了消費者的處理速度,隊列將被填滿,put方法也會被阻塞,如果這時消費者想取消生產者這個任務就無濟於事了,因為生產者阻塞在了put方法中。這個問題很好解決,使用中斷來替代boolean標識。修改生產者代碼:

class PrimeProducer extends Thread{
    
    private final BlockingQueue<BigInteger> queue;
    
    PrimeProducer(BlockingQueue<BigInteger> queue){
        this.queue = queue;
    }
    
    @Override
    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            //條件改為當前線程是否中斷
            while (!Thread.currentThread().isInterrupted()) {
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException e) {
            /*允許線程退出*/
        }
    }
    
    public void cancel(){interrupt();}
}

消費者如果不再需要生產者,可以直接中斷生產者線程,這樣即使生產者處於阻塞狀態,一樣可以退出。由此可見,中斷是實現取消的最合理方式

  

  

java並發基礎(四)--- 取消與關閉