Java并发编程实战:取消和关闭

一、任务取消

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、处理不可中断的阻塞

不可中断阻塞的情况:
(1)java.io中的同步Socket I/O,阻塞I/O最常见的形式是读取和写入Socket。但是InputStream和OutputStream中的read和write方法都不响应中断,只有通过关闭底层的Socket,可以让read或write所阻塞的线程抛出一个SocketException。
(2)java.nio中的同步I/O,中断一个等待InterruptibleChannel的线程,会导致抛出ClosedByInterruptException,并关闭链路。关闭一个InterruptibleChannel导致多个阻塞在链路操作上的线程抛出AsychronousCloseException。
(3)Selector的异步I/O,如果一个线程阻塞于Selector.select方法,close方法会导致它通过抛出ClosedSelectorException提前返回。
以上可以通过覆写Thread的interrupt方法来封装一种非标准的取消,如下代码所示:
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

ExecutorService提供了关闭的两种方法,使用shutdown关闭和使用shutdownNow强行关闭。当通过shutdownNow强行关闭一个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的寿命限定在该方法中。














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值