标签:
1.Thread.interrupt真的能中断线程吗?
在平时的开发过程中,相信都会使用到多线程,在使用多线程时,大家也会遇到各种各样的问题,今天我们就来说说一个多线程的问题——线程中断。在java中启动线程非常容易,大多数情况下我是让一个线程执行完自己的任务然后自己停掉,但是有时候我们需要取消某个操作,比如你在网络下载时,有时候需要取消下载。实现线程的安全中断并不是一件容易的事情,因为Java并不支持安全快速中断线程的机制,这里估计很多同学就会说了,java不是提供了Thread.interrupt 方法中断线程吗,好吧,我们今天就从这个方法开始说起。
但是调用此方法线程真的会停止吗?我们写个demo看看就知道了。
1 package himi.interrupt; 2 3 public class Main { 4 5 private static final String TAG = "Main"; 6 7 public static void main(String[] args) { 8 Thread t = new Thread(new NRunnable()); 9 t.start(); 10 System.out.println("is start......."); 11 try { 12 Thread.sleep(3000); 13 } catch (InterruptedException e) { 14 15 } 16 17 t.interrupt(); 18 System.out.println("is interrupt......."); 19 20 } 21 22 public static class NRunnable implements Runnable { 23 24 public void run() { 25 while (true) { 26 System.out.println("我没有种中断"); 27 try { 28 Thread.sleep(1000); 29 } catch (InterruptedException e) { 30 31 } 32 } 33 } 34 35 } 36 37 }
如果interrutp方法能够中断线程,那么在打印了is interrupt…….之后应该是没有log了,我们看看执行结果吧:
is start.......
我没有种中断
我没有种中断
我没有种中断
is interrupt.......
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
.............
通过结果可以发现子线程并没有中断
所以 Thread.interrupt() 方法并不能中断线程,该方法仅仅告诉线程外部已经有中断请求,至于是否中断还取决于线程自己。在Thread类中除了interrupt() 方法还有另外两个非常相似的方法:interrupted 和 isInterrupted 方法,下面来对这几个方法进行说明:
interrupt 此方法是实例方法,用于告诉此线程外部有中断请求,并且将线程中的中断标记设置为true
interrupted 此方法是类方法,测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
isInterrupted 此方法是实例方法测试线程是否已经中断。线程的中断状态 不受该方法的影响。 线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来
2.处理线程中断的常用方法
(1)常用方法:设置取消标记.
还是用上面的例子,只不过做了些修改:
1 package himi.interrupt; 2 3 public class Main1 { 4 5 public static void main(String[] args) { 6 NRunnable run=new NRunnable(); 7 Thread t=new Thread(run); 8 t.start(); 9 System.out.println("is start......."); 10 try { 11 Thread.sleep(3000); 12 } catch (InterruptedException e) { 13 14 } 15 run.cancel(); 16 System.out.println("cancel ..."+System.currentTimeMillis()); 17 } 18 19 public static class NRunnable implements Runnable 20 { 21 public boolean isCancel=false; 22 23 public void run() { 24 while(!isCancel) 25 { 26 System.out.println("我没有种中断"); 27 try { 28 Thread.sleep(10000); 29 } catch (InterruptedException e) { 30 31 } 32 } 33 System.out.println("我已经结束了..."+System.currentTimeMillis()); 34 } 35 36 public void cancel() 37 { 38 this.isCancel=true; 39 } 40 41 } 42 43 }
执行结果如下:
is start.......
我没有种中断
cancel ...1441334736290
我已经结束了...1441334743309
通过结果,我们发现线程确实已经中断了,但是细心的同学应该发现了一个问题,调用cancel方法和最后线程执行完毕之间隔了好几秒的时间,也就是说线程不是立马中断的,我们下面来分析一下原因:
子线程退出的条件是while循环结束,也就是cancel标示设置为true,但是当我们调用cancel方法将calcel标记设置为true时,while循环里面有一个耗时操作(sleep方法模拟),只有等待耗时操作执行完毕后才会去检查这个标记,所以cancel方法和线程退出中间有时间间隔。
(2)通过interrupt 和 isinterrupt 方法来中断线程
1 package himi.interrupt; 2 3 public class Main2 { 4 5 public static void main(String[] args) { 6 Thread t=new NThread(); 7 t.start(); 8 System.out.println("is start......."); 9 try { 10 Thread.sleep(3000); 11 } catch (InterruptedException e) { 12 13 } 14 System.out.println("start interrupt..."+System.currentTimeMillis()); 15 t.interrupt(); 16 System.out.println("end interrupt ..."+System.currentTimeMillis()); 17 } 18 19 public static class NThread extends Thread 20 { 21 22 @Override 23 public void run() { 24 while(!this.isInterrupted()) 25 { 26 System.out.println("我没有种中断"); 27 try { 28 Thread.sleep(10000); 29 } catch (InterruptedException e) { 30 Thread.currentThread().interrupt(); 31 } 32 } 33 System.out.println("我已经结束了..."+System.currentTimeMillis()); 34 } 35 36 } 37 38 39 }
运行结果:
is start.......
我没有种中断
start interrupt...1441335684254
end interrupt ...1441335684254
我已经结束了...1441335684254
这次是立马中断的,但是这种方法是由局限性的,这种方法仅仅对于会抛出InterruptedException 异常的任务是有效的,比如java中的sleep、wait 等方法,对于不会抛出这种异常的任务其效果其实和第一种方法是一样的,都会有延迟性,这个例子中还有一个非常重要的地方就是catch语句中,我们调用了Thread.currentThread().interrupt() 我们把这句代码去掉,运行你会发现这个线程无法终止,因为在抛出InterruptedException 的同时,线程的中断标志被清除了,所以在while语句中判断当前线程是否中断时,返回的是false.针对InterruptedException 异常,我想说的是:一定不能再catch语句块中什么也不干,如果你实在不想处理,你可以将异常抛出来,让调用抛异常的方法也成为一个可以抛出InterruptedException 的方法,如果自己要捕获此异常,那么最好在catch语句中调用 Thread.currentThread().interrupt(); 方法来让高层只要中断请求并处理该中断。
对于上述两种方法都有其局限性,第一种方法只能处理那种工作量不大,会频繁检查循环标志的任务,对于第二种方法适合用于抛出InterruptedException的代码。也就是说第一种和第二种方法支持的是支持中断的线程任务,那么不支持中断的线程任务该怎么做呢。
例如 如果一个线程由于同步进行I/O操作导致阻塞,中断请求不会抛出InterruptedException ,我们该如何中断此线程呢。
(3)处理不支持中断的线程中断的常用方法:改写线程的interrupt方法
1 public static class ReaderThread extends Thread 2 { 3 public static final int BUFFER_SIZE=512; 4 Socket socket; 5 InputStream is; 6 7 public ReaderThread(Socket socket) throws IOException 8 { 9 this.socket=socket; 10 is=this.socket.getInputStream(); 11 } 12 13 @Override 14 public void interrupt() { 15 try 16 { 17 socket.close(); 18 }catch(IOException e) 19 { 20 21 }finally 22 { 23 super.interrupt(); 24 } 25 super.interrupt(); 26 } 27 @Override 28 public void run() { 29 try 30 { 31 byte[]buf=new byte[BUFFER_SIZE]; 32 while(true) 33 { 34 int count=is.read(buf); 35 if(count<0) 36 break; 37 else if(count>0) 38 { 39 40 } 41 } 42 }catch(IOException e) 43 { 44 45 } 46 } 47 } 48 }
例如在上面的例子中,改写了Thread的interrupt 方法,当调用interrupt 方法时,会关闭socket,如果此时read方法阻塞,那么会抛出IOException 此时线程任务也就结束了。
以上方法是通过改写线程的interrupt 方法实现,那么对于使用线程池的任务该怎么中断呢。
(4)改写线程池的newTaskFor方法
通常我们向线程池中加入一个任务采用如下形式:
Future<?> future=executor.submit(new Runnable(){ @Override public void run() { } });
取消任务时,调用的是future的cancel方法,其实在cancel方法中调用的是线程的interrupt方法。所以对于不支持中断的任务cancel也是无效的,下面我们看看submit方法里面干了上面吧:
public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; }
这里调用的是AbstractExecutorService 的newTaskFor方法,那么我们能不能改写ThreadPoolExecutor的newTaskFor方法呢,接下来看我在处理吧
定义一个基类,所有需要取消的任务继承这个基类:
public interface CancelableRunnable<T> extends Runnable { public void cancel(); public RunnableFuture<T> newTask(); }
将上面的ReaderThread改为继承这个类:
public static class ReaderThread implements CancelableRunnable<Void> { public static final int BUFFER_SIZE=512; Socket socket; InputStream is; public ReaderThread(Socket socket) throws IOException { this.socket=socket; is=this.socket.getInputStream(); } @Override public void run() { try { byte[]buf=new byte[BUFFER_SIZE]; while(true) { int count=is.read(buf); if(count<0) break; else if(count>0) { } } }catch(IOException e) { } } @Override public void cancel() { try { socket.close(); } catch (IOException e) { } } @Override public RunnableFuture<Void> newTask() { return new FutureTask<Void>(this,null) { @Override public boolean cancel(boolean mayInterruptIfRunning) { return super.cancel(mayInterruptIfRunning); if(ReaderThread.this instanceof CancelableRunnable)) { ((CancelableRunnable)(ReaderThread.this)).cancel(); }else { super.cancel(mayInterruptIfRunning); } } }; } }
当你调用future的cancel的方法时,它会关闭socket,最终导致read方法异常,从而终止线程任务。
标签:
原文地址:http://www.cnblogs.com/hebao0514/p/4781380.html