今天學了下多線程中超時任務的處理,這里和大家分享下,遇到了點問題沒能解決,留下來希望大家幫我解疑啊。
在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。
上面的情況和這個是類似的。