在實際的開發過程當中,會遇到這樣的需求:某些功能為了防止系統掛死,需要進行時間控制,超過一定的執行時間,就提示任務執行超時,不再繼續執行該任務,從而保證系統健壯性和穩定性。其實仔細想想,我們可以把這樣的需求,全部歸結為一種“超時控制的業務模型”,建立起自己熟悉的業務模型,以后碰到類似的需求,可以借鑒此方案。若有機會設計或重構系統,在必要的模塊中,也可以將該方案作為增強系統穩定性的一個備選方案。
方案一:使用守護線程
此方案需要的類有:
TimeoutThread:定義超時的線程,里面包含超時時間和是否執行完成狀態定義。主要任務就是監聽時間,超出時間后拋出運行時異常。
TimeoutException:定義的一個運行時異常,繼承RuntimeException。如果覺得名字有重復的話,也可以換成別的名字。
TaskThread:定義任務執行的線程,守護線程,包含成員變量TimeoutThread,里面主要執行任務主體,任務執行完后,修改TimeoutThread的狀態為執行完成。
MyUncauhtExceptionHandler:自定義的線程異常捕獲類,在守護進程由於超時被迫銷毀時,能夠執行這個異常里的代碼,一般用於任務執行主體超時后的狀態改變,如將任務標記為超時狀態。各位請注意:線程中拋出的異常,是不能夠被直接捕獲的。
MyHandlerThreadFactory(可選):實現ThreadFactory接口,線程的創建工廠,在這里主要是為線程池修改默認為線程異常捕獲工廠,若在代碼中設定Thread.setDefaultUncaughtExceptionHandler(new
MyUncauhtExceptionHandler());,則該類可以不用,但一般寫法需要用到該類。建議使用該類
示例代碼如下:
public class TimeoutThread implements Runnable { private long timeout; private boolean isCancel; public TimeoutThread(long timeout) { super(); this.timeout = timeout; } /** * 設定監聽是否取消 */ public void isCancel() { this.isCancel = true; } @Override public void run() { try { Thread.sleep(timeout); if(!isCancel) { throw new TimeoutException("thread is timeout"); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public class TimeoutException extends RuntimeException { private static final long serialVersionUID = 551822143131900511L; /** * 拋出異常信息 * @param str 異常信息 */ public TimeoutException(String str) { super(str); } }
public class TaskThread extends Thread{ private TimeoutThread tt; /** * 需要注入TimeoutThread對象 * 可根據不同的場景,注入不同的對象,完成任務的執行 * @param tt */ public TaskThread(TimeoutThread tt) { this.setDaemon(true); this.tt = tt; } @Override public void run() { try { //這里是任務的執行主體,為了簡單示例,只用sleep方法演示 Thread.sleep(1000); //執行任務完成后,更改狀態 tt.isCancel(); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class MyUncauhtExceptionHandler implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { //簡單起見,只打印一句話 System.out.println("hehe"); } }
public class MyHandlerFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { //定義線程創建工廠,這里主要設定MyUncauhtExceptionHandler Thread t = new Thread(r); t.setUncaughtExceptionHandler(new MyUncauhtExceptionHandler()); return t; } }
public class Client { public static void main(String[] args) { //方式一:直接設定DefaultUncaughtExceptionHandler,然后直接t.start();task.start()啟動線程即可。 Thread.setDefaultUncaughtExceptionHandler(new MyUncauhtExceptionHandler()); //方式二:創建線程創建工廠,利用線程池啟動線程 ExecutorService exec = Executors.newCachedThreadPool(new MyHandlerFactory()); TimeoutThread tt = new TimeoutThread(800); Thread t = new Thread(tt); TaskThread task = new TaskThread(tt); exec.execute(t); exec.execute(task); exec.shutdown(); } }
方案二:使用Future的特性(推薦) 利用Future.get(long timeout, TimeUnit unit)方法。 1、新建TaskThread類,實現Callable接口,實現call()方法。 2、線程池調用submit()方法,得到Future對象。 3、調用Future對象的get(long timeout, TimeUnit unit)方法,該方法的特點:阻塞式線程調用,同時指定了超時時間timeout,get方法執行超時會拋出timeout異常,該異常需要捕獲。 示例代碼:
public class TimeTask implements Callable<String> { @Override public String call() throws Exception { //執行任務主體,簡單示例 Thread.sleep(1000); return "hehe"; } }
ExecutorService exec = Executors.newCachedThreadPool(); Future<String> f = exec.submit(new TimeTask()); try { f.get(200, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } catch (TimeoutException e) { //定義超時后的狀態修改 System.out.println("thread time out"); e.printStackTrace(); }
方案總結: 花了比較多的篇幅介紹方案一,主要目的是了解java 線程的基本處理方案機制,守護線程的特性,線程異常的處理方法。可以通過這個方案,多了解一些java的基礎知識。個人建議不推薦在企業應用開發中使用此方案。 方案二利用現有的Future屬性,在開發過程中直接利用JDK的方法,省時省力,並且可靠性高。