任務和線程的啟動很容易。在大多數情況下我們都會讓他們運行直到結束,或是讓他們自行停止。但是,有時我們希望提前結束任務或是線程,可能是因為用戶請求取消,或是線程在規定時間內沒有結束,或是出現了一些問題迫使線程要提前結束。
1 public class PrimeGenerator implements Runnable { 2 private final List<BigInteger> primes = new ArrayList<>(); 3 // 標志變量,設置為volatile,保證可見性 4 private volatile boolean canceled = false; 5 @Override 6 public void run() { 7 BigInteger p = BigInteger.ONE; 8 // 依靠標志位判斷是否結束線程 9 while(!canceled){ 10 p = p.nextProbablePrime(); 11 synchronized (this){ 12 primes.add(p); 13 } 14 } 15 } 16 // 取消 17 public void cancel(){canceled = true;} 18 //返回結果 19 public synchronized List<BigInteger> get(){ 20 return primes; 21 } 22 }
1 public List<BigInteger> aPrimes() throws InterruptedException { 2 PrimeGenerator generator = new PrimeGenerator(); 3 new Thread(generator).start(); 4 try{ 5 // 睡眠1秒 6 TimeUnit.SECONDS.sleep(1); 7 }finally { 8 // 1秒后取消 9 generator.cancel(); 10 } 11 return generator.get(); 12 }
在上述代碼中,PrimeGenerator采用了簡單的取消策略:客戶代碼通過canceled來請求取消,PrimeGenerator在每次執行搜索前首先檢查是否存在取消請求,如果存在則退出。
1 // 不推薦的寫法 2 public class BrokenPrimeProducer extends Thread { 3 // 阻塞隊列 4 private final BlockingQueue<BigInteger> queue; 5 // 中斷位 6 private volatile boolean canceled = false; 7 8 public BrokenPrimeProducer(BlockingQueue<BigInteger> queue){ 9 this.queue = queue; 10 } 11 12 @Override 13 public void run(){ 14 try { 15 BigInteger p = BigInteger.ONE; 16 while (!canceled) { 17 // PUT操作可能會被阻塞,將無法檢查 canceled 是否變化,因而無法響應退出 18 queue.put(p = p.nextProbablePrime()); 19 } 20 }catch (InterruptedException ex){} 21 } 22 23 public void cancel(){ 24 canceled = true; 25 } 26 }
1 public class Thread{ 2 public void inturrept(){......} 3 public boolean isInterrupted(){......} 4 public static boolean interrupted(){......} 5 }
1 public class PrimeProducer extends Thread { 2 // 阻塞隊列 3 private final BlockingQueue<BigInteger> queue; 4 5 public PrimeProducer(BlockingQueue<BigInteger> queue){ 6 this.queue = queue; 7 } 8 9 @Override 10 public void run(){ 11 try { 12 BigInteger p = BigInteger.ONE; 13 while (!Thread.currentThread().isInterrupted()) { 14 queue.put(p = p.nextProbablePrime()); 15 } 16 }catch (InterruptedException ex){ 17 // 允許退出線程 18 } 19 } 20 21 public void cancel(){ 22 // 中斷 23 interrupt(); 24 } 25 }
中斷是實現取消的最合理方式,在取消之外的其他操作中使用中斷,都是不合理的。
4、中斷策略
中斷策略解釋某個中斷請求:當發現中斷請求時,應該做哪些工作,以多快的速度來響應中斷。任務一般不會在其自己擁有的線程中執行,而是在其他某個服務(比如說,在一個其他線程或是線程池)中執行。對於非線程所有者而言(例如,對線程池來說,任何線程池實現之外的代碼),應該保存並傳遞中斷狀態,使得真正擁有線程的代碼才能對中斷做出響應。
比如說,如果你書寫一個庫函數,一般會拋出InterruptedException作為中斷響應,而不會在庫函數時候把中斷異常捕獲並進行提前處理,而導致調用者被屏蔽中斷。因為你不清楚調用者想要對異常進行何種處理,比如說,是接收中斷后立即停止任務還是進行相關處理並繼續執行任務。中斷的處理必須由該任務自己決定,而不是由其他線程決定。
1 try { 2 // dosomething(); 3 } catch (InterruptedException e) { 4 // 捕獲異常后恢復中斷位 5 Thread.currentThread().interrupt(); 6 e.printStackTrace(); 7 }
1 public interface Future<V> { 2 // 是否取消線程的執行 3 boolean cancel(boolean mayInterruptIfRunning); 4 // 線程是否被取消 5 boolean isCancelled(); 6 //線程是否執行完畢 7 boolean isDone(); 8 // 立即獲得線程返回的結果 9 V get() throws InterruptedException, ExecutionException; 10 // 延時時間后再獲得線程返回的結果 11 V get(long timeout, TimeUnit unit) 12 throws InterruptedException, ExecutionException, TimeoutException; 13 }
1 public static void main(String[] args) { 2 ExecutorService service = Executors.newSingleThreadExecutor(); 3 Future future = service.submit(new TheradDemo()); 4 5 try { 6 // 可能拋出異常 7 future.get(); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } catch (ExecutionException e) { 11 e.printStackTrace(); 12 }finally { 13 //終止任務的執行 14 future.cancel(true); 15 } 16 }
6、關閉ExecutorService