在實際的開發過程當中,會遇到這樣的需求:某些功能為了防止系統挂死,需要進行時間控制,超過一定的執行時間,就提示任務執行逾時,不再繼續執行該任務,進而保證系統健壯性和穩定性。其實仔細想想,我們可以把這樣的需求,全部歸結為一種“逾時控制的業務模型”,建立起自己熟悉的業務模型,以後碰到類似的需求,可以借鑒此方案。若有機會設計或重構系統,在必要的子產品中,也可以将該方案作為增強系統穩定性的一個備選方案。
方案一:使用守護線程
此方案需要的類有:
TimeoutThread:定義逾時的線程,裡面包含逾時時間和是否執行完成狀态定義。主要任務就是監聽時間,超出時間後抛出運作時異常。
TimeoutException:定義的一個運作時異常,繼承RuntimeException。如果覺得名字有重複的話,也可以換成别的名字。
TaskThread:定義任務執行的線程,守護線程,包含成員變量TimeoutThread,裡面主要執行任務主體,任務執行完後,修改TimeoutThread的狀态為執行完成。
MyUncauhtExceptionHandler:自定義的線程異常捕獲類,在守護程序由于逾時被迫銷毀時,能夠執行這個異常裡的代碼,一般用于任務執行主體逾時後的狀态改變,如将任務标記為逾時狀态。各位請注意:線程中抛出的異常,是不能夠被直接捕獲的。
MyHandlerThreadFactory(可選):實作ThreadFactory接口,線程的建立工廠,在這裡主要是為線程池修改預設為線程異常捕獲工廠,若在代碼中設定Thread.setDefaultUncaughtExceptionHandler(new MyUncauhtExceptionHandler());,則該類可以不用,但一般寫法需要用到該類。建議使用該類
Client:用于示範效果的類。
示例代碼如下:
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的方法,省時省力,并且可靠性高。