1. 程式人生 > >使用Future停止超時任務

使用Future停止超時任務

是否 測試 路徑名 結果 在那 解釋 gin log 文件的

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

在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。

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

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

《JAVA中的相對路徑和絕對路徑》原文:

File類是用來構造文件或文件夾的類,在其構造函數中要求傳入一個String類型的參數,用於指示文件所在的路徑.以前一直使用絕對路徑作為參數,其實這裏也可以使用相對路徑.使用絕對路徑不用說,很容易就能定位到文件,那麽使用了相對路徑jvm如何定位文件的呢?

按照jdk Doc上的說法”絕對路徑名是完整的路徑名,不需要任何其他信息就可以定位自身表示的文件。相反,相對路徑名必須使用來自其他路徑名的信息進行解釋。默認情況下,java.io 包中的類總是根據當前用戶目錄來分析相對路徑名。此目錄由系統屬性 user.dir 指定,通常是 Java 虛擬機的調用目錄.”

相對路徑顧名思義,相對於某個路徑,那麽究竟相對於什麽路徑我們必須弄明白.按照上面jdk文檔上講的這個路徑是”當前用戶目錄”也就是”java虛擬機的調用目錄”.更明白的說這個路徑其實是我們在哪裏調用jvm的路徑.舉個例子:

假設有一java源文件Example.java在d盤根目錄下,該文件不含package信息.我們進入命令行窗口,然後使用”d:”命令切換到d盤根目錄下,然後用”javac Example.java”來編譯此文件,編譯無錯後,會在d盤根目錄下自動生成”Example.class”文件.我們在調用”java Example”來運行該程序.此時我們已經啟動了一個jvm,這個jvm是在d盤根目錄下被啟動的,所以此jvm所加載的程序中File類的相對路徑也就是相對這個路徑的,即d盤根目錄:D:\.同時” 當前用戶目錄”也是D:\.在System.getProperty(“user.dir”);系統變量”user.dir”存放的也是這個值.

我們可以多做幾次試驗,把”Example.class”移動到不同路徑下,同時在那些路徑下,執行”java Example”命令啟動jvm,我們會發現這個”當前用戶目錄”是不斷變化的,它的路徑始終和我們在哪啟動jvm的路徑是一致的.

搞清了這些,我們可以使用相對路徑來創建文件,例如:

File file = new File(“a.txt”);

File.createNewFile();

假設jvm是在”D:\”下啟動的,那麽a.txt就會生成在D:\a.txt;

此外,這個參數還可以使用一些常用的路徑表示方法,例如”.”或”.\”代表當前目錄,這個目錄也就是jvm啟動路徑.所以如下代碼能得到當前目錄完整路徑:

File f = new File(“.”);

String absolutePath = f.getAbsolutePath();

System.out.println(absolutePath);//D:\

最後要說說在eclipse中的情況:

Eclipse中啟動jvm都是在項目根路徑上啟動的.比如有個項目名為blog,其完整路徑為:D:\work\IDE\workspace\blog.那麽這個路徑就是jvm的啟動路徑了.所以以上代碼如果在eclipse裏運行,則輸出結果為” D:\work\IDE\workspace\blog.”

Tomcat中的情況.

如果在tomcat中運行web應用,此時,如果我們在某個類中使用如下代碼:

File f = new File(“.”);

String absolutePath = f.getAbsolutePath();

System.out.println(absolutePath);

那麽輸出的將是tomcat下的bin目錄.我的機器就是” D:\work\server\jakarta-tomcat-5.0.28\bin\.”,由此可以看出tomcat服務器是在bin目錄下啟動jvm的.其實是在bin目錄下的”catalina.bat”文件中啟動jvm的.

使用Future停止超時任務