處理不可中斷的阻塞(java併發編程第7章)

並不是所有的阻塞都是可中斷的, 比如InputStream.read方法. 在檢測到輸入數據可用, 到達流末尾或者拋出異常前, 該方法一直阻塞. 而且阻塞的時候不會檢查中斷標記, 所以中斷線程無法使read從阻塞狀態返回. 但是關閉流可以使得read方法拋出異常, 從而從阻塞狀態返回. 

Java代碼  收藏代碼
  1. public class ReaderThread extends Thread {  
  2.     private static final int BUFSZ = 1024;  
  3.     private final Socket socket;  
  4.     private final InputStream in;  
  5.   
  6.     public ReaderThread(Socket socket) throws IOException {  
  7.         this.socket = socket;  
  8.         this.in = socket.getInputStream();  
  9.     }  
  10.   
  11.     // 覆蓋Thread類的interrupt方法, 加入關閉socket的代碼  
  12.     // 如果發生中斷時, 線程阻塞在read方法上, socket的關閉會導致read方法拋出SocketException, 然後run方法運行完畢  
  13.     public void interrupt() {  
  14.         try {  
  15.             socket.close();  
  16.         } catch (IOException ignored) {  
  17.         } finally {  
  18.             super.interrupt();  
  19.         }  
  20.     }  
  21.   
  22.     public void run() {  
  23.         try {  
  24.             byte[] buf = new byte[BUFSZ];  
  25.             while (true) {  
  26.                 int count = in.read(buf);  
  27.                 if (count < 0)  
  28.                     break;  
  29.                 else if (count > 0)  
  30.                     processBuffer(buf, count);  
  31.             }  
  32.         } catch (IOException e) { /*  Allow thread to exit  */  
  33.         }  
  34.     }  
  35.   
  36.     private void processBuffer(byte[] buf, int count) {  
  37.         //...  
  38.     }  
  39. }  

如果task並非在自己創建的線程裏運行, 而是提交給線程池運行的話, 就無法使用上例的方式處理不可中斷阻塞了. 之前有過分析, 對於提交給線程池執行的task, 應該通過Future.cancel方法提前終止task的運行, 所以可以考慮重寫Future.cancel方法, 在其中加入關閉socket的操作. Future對象是由submit方法返回的, 其源代碼如下:

Java代碼  收藏代碼
  1. public <T> Future<T> submit(Callable<T> task) {  
  2.     if (task == null)   
  3.         throw new NullPointerException();  
  4.     RunnableFuture<T> ftask = newTaskFor(task);  
  5.     execute(ftask);  
  6.     return ftask;  
  7. }   

可知submit方法返回的Future對象是調用newTaskFor方法獲得的:

Java代碼  收藏代碼
  1. protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {  
  2.     return new FutureTask<T>(callable);  
  3. }   

newTaskFor方法被聲明爲protected, 所以我們可以通過繼承覆蓋該方法, 返回自定義的Future對象.

首先將需要覆蓋的2個方法定義在接口中:

Java代碼  收藏代碼
  1. public interface CancellableTask<T> extends Callable<T> {   
  2.     void cancel();   
  3.     RunnableFuture<T> newTask();   
  4. }    

然後讓task類實現CancellableTask接口:

Java代碼  收藏代碼
  1. public abstract class SocketUsingTask<T> implements CancellableTask<T> {  
  2.     private Socket socket;  
  3.   
  4.     protected synchronized void setSocket(Socket s) {  
  5.         socket = s;  
  6.     }  
  7.   
  8.     public synchronized void cancel() {  
  9.         try {  
  10.             if (socket != null)  
  11.                 socket.close();  
  12.         } catch (IOException ignored) {  
  13.         }  
  14.     }  
  15.   
  16.     public RunnableFuture<T> newTask() {  
  17.         return new FutureTask<T>(this) {  
  18.             // 定義FutureTask的匿名內部類, 並覆蓋cancel方法, 向其中加入關閉socket的操作  
  19.             public boolean cancel(boolean mayInterruptIfRunning) {  
  20.                 try {  
  21.                     SocketUsingTask.this.cancel();  
  22.                 } finally {  
  23.                     return super.cancel(mayInterruptIfRunning);  
  24.                 }  
  25.             }  
  26.         };  
  27.     }  
  28. }  

接着繼承ThreadPoolExecutor類並覆蓋newTaskFor方法, 讓該方法返回自定義的FutureTask對象:

Java代碼  收藏代碼
  1. public class CancellingExecutor extends ThreadPoolExecutor {  
  2.     // ...  
  3.     protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {  
  4.         // 如果callable是CancellableTask對象, 那麼就返回自定義的FutureTask(通過調用其newTaskFor方法實現)  
  5.         if (callable instanceof CancellableTask)  
  6.             return ((CancellableTask<T>) callable).newTask();  
  7.         else  
  8.             return super.newTaskFor(callable);  
  9.     }  
  10. }  

測試代碼:

Java代碼  收藏代碼
  1. public static void main(String[] args) {  
  2.     CancellingExecutor executor = new CancellingExecutor();  
  3.     SocketUsingTask task = new SocketUsingTask();  
  4.     task.setSocket(new Socket("www.baidu.com"80));  
  5.     Future<V> future = executor.submit(task);  
  6.     future.cancel(true);  
  7. }  

處理不可中斷的阻塞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章