除非你知道线程的中断策略,否则你不应该中断线程。
任务执行线程由标准的Executor实现创建,他实现了一个中断策略使得任务可以通过中断被取消,所以当他们在标准Executor中运行时通过他们的Future来取消任务,可以设置cancel的参数值为true。当尝试取消一个任务的时候,你不应该直接中断线程池,因为你不知道中断请求到达时什么任务正在运行——只能通过任务的Future来做这件事情。
public static void timedRun(Runnable r,long timeout,TimeUnit unit) throws InterruptedException{
Future<?> task=taskExec.submit(r);
try{
task.get(timeout,unit);
}catch(TimeoutException e){
}catch(ExecutionException e){
throw launderThrowable(e.getCause());
}finally{
task.cancel(true);
}
}
上面的例子中为了简化代码,在 finally 中无条件的调用了 Future.cancel,这样做是基于下面的事实:取消一个已完成的任务不会有任何影响。
当Future.get抛出InterruptedException或TimeoutException时,如果你知道不在需要结果时,就可以调用Future.cancel来取消任务了。
并不是所有的阻塞方法或阻塞机制都相应中断,如果一个线程是由于进行同步Socket I/O或者等待获得内部锁而阻塞的,那么中断除了能够设置线程的中断状态以外,什么都改变不了。
java.io中的同步Socket I/O在服务器应用程序中,阻塞I/O最常见的形式是读取和写入Socket。但是InputStream和OutputStream中的read和write方法都不响应中断,但是通过关闭底层的socket可以让read和write所阻塞的线程抛出一个SocketException。
java.nio中的同步I/O,中断一个等待InterruptibleChannel的线程,会导致抛出ClosedByInterruptException,并不关闭链路(也会导致其他线程在这条链路的阻塞)。关闭一个InterruptibleChannel导致多个阻塞在链路操作上的线程抛出AsynchronousCloseException。大多数标准Channels都实现InterruptibleChannel。
Selector的异步I/O。如果一个线程阻塞于Selector.select方法,close方法会导致他通过ClosedSelectorException提前返回。
public 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 ignored){
}finally{
super.interrupt();
}
}
public void run(){
try{
byte[] buf=new byte[BUFSZ];
while(true){
int count=in.read(buf);
if(count<0){
break;
}else if(count>0){
processBuffer(buf,count);
}
}
}catch(IOException e){
}
}
}
上面的例子中,通过重写interrupt来封装非标准取消。我们也可以使用newTaskFor钩子函数来改进用来封装非标准取消的方法,这是Java6中添加到THreadPoolExecutor的新特性。当提交一个Callable个ExecutorService时,返回一个Future,可以用Future来取消任务。newTaskFor钩子是一个工厂方法,创建Future来代表任务,他返回一个RunnableFuture,这是一个接口,扩展了Future和Runnable。
自定义的任务Future允许你重写Future.cancel方法。自定义的取消代码可以实现日志或者收集取消的统计信息,并可以用来取消那些不响应中断的活动。
通过重写interrupt,上面的例子封装了使用Socket的线程的取消行为,同样的事情也可以通过重写任务的Future.cancel方法来实现。
public interface CancellableTask<T> extends Callable<T>{
void cancel();
RunnableFuture<T> newTask();
}
public class CancellingExecutor extends ThreadPoolExecutor{
protected<T> RunnableFuture<T> newTaskFor(Callable<T> callable){
if(callable instanceof CancellableTask){
return ((CancellableTask<T>)callable).newTask();
}else{
return super.newTaskFor(callable);
}
}
}
public abstract class SocketUsingTask<T> implements CancellableTask<T>{
private Socket socket;
protected synchronized void setSocket(Socket s){
socket=s;
}
public synchronized void cancel(){
try{
if(socket!=null){
socket.close();
}
}catch(IOException ignored){
}
}
public RunnableFuture<T> newTask(){
return new FutureTask<T>(this){
public boolean cancel(boolean mayInterruptIfRunning){
try{
SocketUsingTask.this.cancel();
}finally{
return super.cancel(mayInterruptIfRunning);
}
}
}
}
}
对于线程持有的服务,只要服务的存在时间大于创建线程的方法存在的时间,那么就应该提供生命周期方法。
PrintWriter这样的字符流类时线程安全的,但是如果在单一日志消息中写入多行则需要在客户端加锁,来避免多线程交替输出。
public class LogWriter{
private final BlockingQueue<String> queue;
private final LoggerThread logger;
public LogWriter(Writer writer){
this.queue=new LinkedBlockingQueue<String>(CAPACITY);
this.logger=new LoggerThread(writer);
}
public void start(){
logger.start();
}
public void log(String msg)throws InterruptedException{
queue.put(msg);
}
private class LoggerThread extends Thread{
private final PrintWriter writer;
public void run(){
try{
while(true){
writer.println(quque.take());
}
}catch(InterruptedException ignored){
}finally{
writer.close();
}
}
}
}
上面是一个不支持关闭的生产者 - 消费者日志服务
public void log(String msg)throws InterruptException{
if(!shtudownRequested){
queue.put(msg);
}else{
throw new IllegalStateException(“logger is shut down”);
}
}
上面是一个检查再运行的例子,并不安全。
因为put是阻塞的,所以我们不可以直接以加锁的方式解决问题。
public class LogService{
private final BlockingQueue<String> queue;
private final LoggerThread loggerThread;
private final PrintWriter writer;
private boolean isShutdown;
private int reservations;
public void start(){
loggerThread.start();
}
public void stop(){
synchronized(this){
isShutdown=true;
}
loggerThread.interrupt();
}
public void log(String msg)throws InterruptedException{
synchronized(this){
if(isShutdown){
throw new IllegalStateException(...);
}
++reservations;
}
queue.put(msg);
}
private class LoggerThread extends Thread{
public void run(){
try{
while(true){
try{
synchronized(LogService.this){
if(isShutdown&&reservations==0){
break;
}
}
String msg=queue.take();
synchronized(LogService.this){
--reservations;
}
writer.println(msg);
}catch(InterruptedException e){
}finally{
writer.close();
}
}
}
}
}
}
上面的例子中添加了可靠的取消。