在实际的开发过程当中,会遇到这样的需求:某些功能为了防止系统挂死,需要进行时间控制,超过一定的执行时间,就提示任务执行超时,不再继续执行该任务,从而保证系统健壮性和稳定性。其实仔细想想,我们可以把这样的需求,全部归结为一种“超时控制的业务模型”,建立起自己熟悉的业务模型,以后碰到类似的需求,可以借鉴此方案。若有机会设计或重构系统,在必要的模块中,也可以将该方案作为增强系统稳定性的一个备选方案。
方案一:使用守护线程
此方案需要的类有:
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的方法,省时省力,并且可靠性高。