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