使用Future停止超時任務
今天學了下多線程中超時任務的處理,這裏和大家分享下,遇到了點問題沒能解決,留下來希望大家幫我解疑啊。
在JAVA中停止線程的方法有多種,有一種是結合ExecutorService和Future的使用,停止在線程池中超時的任務。
這種情況下處理的都是比較耗時的操作,比如請求資源,數據庫查詢等,當超過一定時間沒有返回結果,就結束線程,提高響應速度。
具體步驟如下:
- 實現Runnable接口或者Callable接口,分別實現run方法或者call方法,在方法中執行耗時操作。
- 將步驟1中的耗時操作線程放入到線程池中(通過ExecutorService.submit方法),這個線程池由ExecutorService來管理。
- 步驟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停止超時任務