使用Future停止超時任務


今天學了下多線程中超時任務的處理,這里和大家分享下,遇到了點問題沒能解決,留下來希望大家幫我解疑啊。

在JAVA中停止線程的方法有多種,有一種是結合ExecutorService和Future的使用,停止在線程池中超時的任務。
這種情況下處理的都是比較耗時的操作,比如請求資源,數據庫查詢等,當超過一定時間沒有返回結果,就結束線程,提高響應速度。

具體步驟如下:

  1. 實現Runnable接口或者Callable接口,分別實現run方法或者call方法,在方法中執行耗時操作。
  2. 將步驟1中的耗時操作線程放入到線程池中(通過ExecutorService.submit方法),這個線程池由ExecutorService來管理。
  3. 步驟2中的submit方法會返回一個Future對象,這個Future對象得到耗時操作的執行結果。Future的get(long timeout, TimeUnit unit)將會在指定的時間內去獲得執行結果,如果操作還沒執行完,就會拋出TimeoutException,在捕獲這個超時異常時就可以取消耗時任務的執行(通過Future.cancel(true)).

 

下面是一個下載大文件的程序,如果下載任務超時將關閉任務。
(DownloadTask.java: 下載任務)

 1 class DownloadTask implements Runnable {
 2     private String filename;
 3     // 接收在run方法中捕獲的異常,然后自定義方法拋出異常
 4     private Throwable exception;
 5     //是否關閉此下載任務
 6     private boolean isStop = false;
 7     
 8     public void setStop(boolean isStop) {
 9         this.isStop = isStop;
10     }
11 
12     public DownloadTask(String filename) {
13         this.filename = filename;
14     }
15 
16     /**
17      * 下載大數據
18      * 
19      * @param filename
20      * @throws FileNotFoundException
21      *             , IOException
22      */
23     private void download() throws FileNotFoundException, IOException {
24         File file = new File(filename);
25         File saveFile = null;
26         String[] names = filename.split("/");
27         String saveName = names[names.length - 1];
28         saveFile = new File("tmp/" + saveName);
29         InputStream input = new FileInputStream(file);
30         OutputStream output = new FileOutputStream(saveFile);
31 
32         // 進行轉存
33         int len = 0;
34         byte[] buffer = new byte[1024];
35         while (-1 != (len = input.read(buffer, 0, buffer.length))) {
36             if(isStop)
37                 break;
38             output.write(buffer, 0, len);
39         }
40 
41         input.close();
42         output.close();
43     }
44 
45     public void throwException() throws FileNotFoundException, IOException {
46         if (exception instanceof FileNotFoundException)
47             throw (FileNotFoundException) exception;
48         if (exception instanceof IOException)
49             throw (IOException) exception;
50     }
51 
52     @Override
53     public void run() {
54         try {
55             download();
56         } catch (FileNotFoundException e) {
57             exception = e;
58         } catch (IOException e) {
59             exception = e;
60         }
61     }
62 }

 注意到第4行定義了一個Throwable對象,這是因為在線程類的run方法中是沒辦法拋出異常的,要讓外部獲得程序的運行狀況,就通過手動賦值和拋出,第45~49行定義了拋出異常的方法。(任務的業務邏輯不應該在內部處理掉,而應該在外部判斷進行控制,個人理解)

(LoadSomething.java: 啟動下載任務和處理異常)

 1 /**
 2  * 使用Futrue來取消任務,模擬下載文件超時時取消任務
 3  * 
 4  * @author hongjie
 5  * 
 6  */
 7 public class LoadSomething {
 8     // 線程池服務接口
 9     private ExecutorService executor;
10 
11     
12     public LoadSomething() {
13         executor = Executors.newSingleThreadExecutor();
14     }
15 
16     public void beginToLoad(DownloadTask task, long timeout,
17             TimeUnit timeType) {
18         Future<?> future = executor.submit(task);
19         try {
20             future.get(timeout, timeType);
21             task.throwException();
22         } catch (InterruptedException e) {
23             System.out.println("下載任務已經取消");
24         } catch (ExecutionException e) {
25             System.out.println("下載中發生錯誤,請重新下載");
26         } catch (TimeoutException e) {
27             System.out.println("下載超時,請更換下載點");
28         } catch (FileNotFoundException e) {
29             System.out.println("請求資源找不到");
30         } catch (IOException e) {
31             System.out.println("數據流出錯");
32         } finally {
33             task.setStop(true);
34             // 因為這里的下載測試不用得到返回結果,取消任務不會影響結果
35             future.cancel(true);
36         }
37     }
38 }

第18行將下載任務放入了線程池中並且開始執行下載任務,第20行中在制定時間內去獲得結果,如果超時將會拋出TimeoutException, 可以看到21行是拋出下載任務中所可能遇到的異常。最后在finally塊中,調用future.cancle停掉任務。這里不用ExecutorService.shutdown方法是因為可能在線程池中有其他線程正在執行任務。
我遇到的問題:在33行中我加了個task.setStop(true)方法,這里通過改變控制變量狀態來停止下載任務的執行,這種方法是可以停止掉下載任務。但是如果不用這種改變控制變量的方法,用future.cancel方法應該是能停掉下載任務的。但實際執行結果是拋出了超時異常,future.cancel返回了true,即任務已取消,但文件還是會繼續下載下來,我想會不會是這樣,文件下載是IO阻塞的,當下載任務執行的時候,線程會阻塞在那里,所以關不掉??希望知道的朋友指點一下我啊。

 

(LoadTest.java: 測試主函數)

1 public class LoadTest {
2     public static void main(String[] args) {
3         LoadSomething load = new LoadSomething();
4         DownloadTask downloadTask = new DownloadTask("G:/Games/5211install.exe");
5         //開始下載,並設定超時限額為3毫秒
6         load.beginToLoad(downloadTask, 3, TimeUnit.MILLISECONDS);
7     }
8 }

 

程序運行結果:
源文件:

拋出異常:

目標文件:

 

之前對相對路徑和絕對路徑不是很了解,今天查了一下,有幾篇好文章,個人覺得挺不錯,也和大家分享下:
JAVA中的相對路徑和絕對路徑(和虛擬機JVM有關)

 

問題我已經找到答案了:
在java庫中,許多可阻塞的方法都是通過提前返回或者拋出InterruptedException來響應中斷請求的,從而使開發人員更容易構建出能響應取消請求的任務。然而並非所有的可阻塞方法或者阻塞機制都能響應中斷;如果一個線程由於執行同步的Socket I/O或者等待獲得內置鎖而阻塞,那么中斷請求只能設置線程的中斷狀態,除此之外沒有其他任何作用。

在服務應用程序中,最常見的阻塞I/O形式就是對套接字進行讀取和寫入。雖然InputStream和fOutputStream中的read和write等方法都不會響應中斷,但通過關閉底層的套接字,可以使得由於執行read和write等方法而被阻塞的線程拋出一個socketException。

上面的情況和這個是類似的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM