一、任务取消
1、设置cancelled标志
在程序中声明volatile类型的cancelled标志,当任务调用者设置cancelled为true时,任务在运行过程中通过不断检查是否被取消可以得知任务被取消的消息,从而不再执行任务。如下代码所示:
class PrimeGenerator implements Runnable{
private final List<BigInteger> primes = new ArrayList<BigInteger>();
private volatile boolean cancelled;
public void run(){
BigInteger p = BigInteger.ONE;
while(!cancelled){
p = p.nextProbablePrime();
synchronized(this){
primes.add(p);
}
}
}
public void cancel(){
cancelled = true;
}
public synchronized List<BigInteger> get(){
return new ArrayList<BigInteger>(primes);
}
}
public class TestClass{
List <BigInteger> aSendOfPrimes() throws InterruptedException{
PrimeGenerator generator = new PrimeGenerator();
new Thread(generator).start();
try{
Thread.sleep(1000);
}finally {
generator.cancel();
}
return generator.get();
}
}
2、通过使用中断进行取消
对于阻塞库函数,比如:Thread.sleep和Object.wait,如果还使用检测标志的方法,就可能会导致在执行cancel函数后的很长时间内得不到影响,因为此时线程或许刚好被阻塞了(或许是在沉睡,或许是在等待其他任务等等)。这时就要通过中断线程来取消任务的执行了,阻塞库函数对中断的响应表现为:清除中断状态,抛出InterruptedException, 这表示阻塞操作因为中断的缘故提前结束。如下代码所示:
class PrimeGenerator extends Thread{
private final BlockingQueue<BigInteger> queue ;
public PrimeGenerator(BlockingQueue<BigInteger> queue){
this.queue = queue;
}
public void run(){
try{
BigInteger p = BigInteger.ONE;
while(!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
}catch(InterruptedException consumed){
/*允许线程退出*/
}
}
public void cancel(){
interrupt();
}
}
3、响应中断
当调用可中断的阻塞函数时,比如Thread.sleep或者BlockingQueue.put,有两种处理InterruptedException的实用策略:
(1)传递异常:传递InterruptedException只需要简单地把InterruptedException添加到throws子句中。
(2)保存中断状态: 实现这个目的的标准方法是再次调用interrupt来恢复中断状态。不应该掩盖InterruptedException,不要在catch块中捕获到异常却什么也不做。如果有些任务不支持使用取消标志(以上第一种方式),但是调用了可中断的阻塞方法,那么必须在循环中调用这些可中断的阻塞方法,当发现中断后重新尝试,并在本地保存中断状态,在返回前恢复状态,而不是立刻捕获InterruptedException。过早设置中断可能会引起无限循环,因为大多数可中断的阻塞方法在入口时检查中断状态,并且如果该状态已被设置,那么就会立刻抛出InterruptedException。如下代码所示:
public Task getNextTask(BlockingQueue<>){
boolean interrupted = false;
try{
try{
return queue.take();
}catch(InterruptedException e){
interrupted = true;
//重新尝试
return queue.take();
}
}finally{
if(interrupted)
Thread.currentThread().interrupt();
}
}
4、定时中断
通过使用ScheduledFuture在规定的时间取消任务,可以避免因为任务已经结束再进行中断操作带来的风险。如下代码中,用来执行任务的线程拥有自己的执行策略,即使任务不响应中断,限时运行的方法仍然能够返回它的调用者。而如果在规定的时间内任务已经提前执行完毕,那等到规定的时间后依然会正常返回到调用者。
private ScheduledThreadPoolExecutor cancelExec = new ScheduledThreadPoolExecutor(1);
public void timedRun(final Runnable r,long timeout,TimeUnit unit) throws InterruptedException{
class RethrowableTask implements Runnable{
private volatile Throwable t;
public void run(){
try{
r.run();
}catch(Throwable t){
this.t = t;
}
}
public void rethrow(){
if(t != null)
throw t;
}
}
RethrowableTask task = new RethrowableTask();
final Thread taskThread = new Thread(task);
taskThread.start();
cancelExec.schedule(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
taskThread.interrupt();
}
},timeout,unit);
taskThread.join(unit.toMillis(timeout));
task.rethrow();
}
5、通过Future取消
ExecutorService.submit会返回一个Future来描述任务。Future有一个cancel方法,它需要一个boolean类型的参数,它的返回值表示取消尝试是否成功(这仅仅是说它是否能够接收中断,而不是任务是否检测并处理了中断)。当参数为true时,并且任务当前正在运行于一些线程中,那么这个线程是应该中断的。参数为false意味着如果还没有启动的话,不要运行这个任务,这应该用于那些不处理中断的任务。当任务在标准Executor中运行时,要通过它们的Future来取消任务。如下代码示例:
public void timedRun(final Runnable r,long timeout,TimeUnit unit) throws InterruptedException{
Future<?> task = taskExec.submit(r);
try{
task.get(timeout,unit);
}catch(TimeoutException e){
//下面任务会被取消
}catch(ExecutionException e){
//task中抛出的异常;重抛出
throw launderThrowable(e.getCause());
}finally{
task.cancel(true);
}
}
6、处理不可中断的阻塞
class ReaderThread extends Thread{
private final Socket socket;
private final InputStream in;
public ReaderThread(Socket socket) throws IOException{
this.socket = socket;
this.in = socket.getInputStream();
}
public void interrupt(){
try {
socket.close();
} catch (IOException e) {
}finally{
super.interrupt();
}
}
public void run(){
try{
byte[] buf = new byte[1024];
while(true){
int count = in.read(buf);
if(count < 0)
break;
else if(count > 0)
processBuffer(buf,count);
}
}catch(IOException e){
socket.close();
}
}
}
二、关闭ExecutorService
class TrackingExecutor extends AbstractExecutorService{
private final ExecutorService exec;
private final Set<Runnable> tasksCancelledAtShutdown = Collections.synchronizedSet(new HashSet<Runnable>());
public List<Runnable> getCancelledTasks() {
if(!exec.isTerminated())
throw new IllegalStateException();
return new ArrayList<Runnable>(tasksCancelledAtShutdown);
}
public void execute(final Runnable runnable){
exec.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try{
runnable.run();
}finally{
if(isShutdown() && Thread.currentThread().isInterrupted())
tasksCancelledAtShutdown.add(runnable);
}
}
});
}
/*覆写其他方法*/
}
如果一个方法需要处理一批任务,并在所有任务结束前不会返回,那么它可以通过使用私有的Executor来简化服务的生命周期管理,其中Executor的寿命限定在该方法中。