天天看點

java多線程實作任務逾時監聽

在實際的開發過程當中,會遇到這樣的需求:某些功能為了防止系統挂死,需要進行時間控制,超過一定的執行時間,就提示任務執行逾時,不再繼續執行該任務,進而保證系統健壯性和穩定性。其實仔細想想,我們可以把這樣的需求,全部歸結為一種“逾時控制的業務模型”,建立起自己熟悉的業務模型,以後碰到類似的需求,可以借鑒此方案。若有機會設計或重構系統,在必要的子產品中,也可以将該方案作為增強系統穩定性的一個備選方案。

方案一:使用守護線程

此方案需要的類有:

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的方法,省時省力,并且可靠性高。