天天看点

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