1.Thread.interrupt真的能中断线程吗?

      在平时的开发过程中,相信都会使用到多线程,在使用多线程时,大家也会遇到各种各样的问题,今天我们就来说说一个多线程的问题——线程中断。在java中启动线程非常容易,大多数情况下我是让一个线程执行完自己的任务然后自己停掉,但是有时候我们需要取消某个操作,比如你在网络下载时,有时候需要取消下载。实现线程的安全中断并不是一件容易的事情,因为Java并不支持安全快速中断线程的机制,这里估计很多同学就会说了,java不是提供了Thread.interrupt 方法中断线程吗,好吧,我们今天就从这个方法开始说起。

但是调用此方法线程真的会停止吗?我们写个demo看看就知道了。

 package himi.interrupt;

 public class Main {

     private static final String TAG = "Main";

     public static void main(String[] args) {
Thread t = new Thread(new NRunnable());
t.start();
System.out.println("is start.......");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { } t.interrupt();
System.out.println("is interrupt......."); } public static class NRunnable implements Runnable { public void run() {
while (true) {
System.out.println("我没有种中断");
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
}
} } }

如果interrutp方法能够中断线程,那么在打印了is interrupt…….之后应该是没有log了,我们看看执行结果吧:

is start.......
我没有种中断
我没有种中断
我没有种中断
is interrupt.......
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
我没有种中断
.............

 

通过结果可以发现子线程并没有中断

所以 Thread.interrupt() 方法并不能中断线程,该方法仅仅告诉线程外部已经有中断请求,至于是否中断还取决于线程自己。在Thread类中除了interrupt() 方法还有另外两个非常相似的方法:interrupted 和 isInterrupted 方法,下面来对这几个方法进行说明:

  • interrupt 此方法是实例方法,用于告诉此线程外部有中断请求,并且将线程中的中断标记设置为true

  • interrupted 此方法是类方法,测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

  • isInterrupted 此方法是实例方法测试线程是否已经中断。线程的中断状态 不受该方法的影响。 线程中断被忽略,因为在中断时不处于活动状态的线程将由此返回 false 的方法反映出来

2.处理线程中断的常用方法

(1)常用方法:设置取消标记.

还是用上面的例子,只不过做了些修改:

 package himi.interrupt;

 public class Main1 {

     public static void main(String[] args) {
NRunnable run=new NRunnable();
Thread t=new Thread(run);
t.start();
System.out.println("is start.......");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { }
run.cancel();
System.out.println("cancel ..."+System.currentTimeMillis());
} public static class NRunnable implements Runnable
{
public boolean isCancel=false; public void run() {
while(!isCancel)
{
System.out.println("我没有种中断");
try {
Thread.sleep(10000);
} catch (InterruptedException e) { }
}
System.out.println("我已经结束了..."+System.currentTimeMillis());
} public void cancel()
{
this.isCancel=true;
} } }

执行结果如下:

is start.......
我没有种中断
cancel ...1441334736290
我已经结束了...1441334743309

 

     通过结果,我们发现线程确实已经中断了,但是细心的同学应该发现了一个问题,调用cancel方法和最后线程执行完毕之间隔了好几秒的时间,也就是说线程不是立马中断的,我们下面来分析一下原因:

子线程退出的条件是while循环结束,也就是cancel标示设置为true,但是当我们调用cancel方法将calcel标记设置为true时,while循环里面有一个耗时操作(sleep方法模拟),只有等待耗时操作执行完毕后才会去检查这个标记,所以cancel方法和线程退出中间有时间间隔。

 

(2)通过interrupt 和 isinterrupt 方法来中断线程

 package himi.interrupt;

 public class Main2 {

     public static void main(String[] args) {
Thread t=new NThread();
t.start();
System.out.println("is start.......");
try {
Thread.sleep(3000);
} catch (InterruptedException e) { }
System.out.println("start interrupt..."+System.currentTimeMillis());
t.interrupt();
System.out.println("end interrupt ..."+System.currentTimeMillis());
} public static class NThread extends Thread
{ @Override
public void run() {
while(!this.isInterrupted())
{
System.out.println("我没有种中断");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("我已经结束了..."+System.currentTimeMillis());
} } }

运行结果:

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方法

 public static class ReaderThread extends Thread
{
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 interrupt() {
try
{
socket.close();
}catch(IOException e)
{ }finally
{
super.interrupt();
}
super.interrupt();
}
@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)
{ }
}
}
}

例如在上面的例子中,改写了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方法异常,从而终止线程任务。

Java基础知识强化16:深入分析Java线程中断机制的更多相关文章

  1. Java基础知识强化99:Java 常见异常及趣味解释

    常见 Java 异常解释:(译者注:非技术角度分析.阅读有风险,理解需谨慎:) 1. java.langjava.lang软件包是java语言的核心部分,它提供了java中的基础类. java.lan ...

  2. Java基础知识强化10:Java中的中间缓存变量机制

    1.对于自增运算++j与j++,由于加一的执行顺序不同,所以Java中有中间缓存变量来储存其单个表达式的值,而j的自增自减的结果依然保留在原来的变量储存区.因为本体是j的值,而单个表达式的值是中间产生 ...

  3. Java基础知识强化106:Java中 int 的各进制之间的转换

    1.二.八.十.十六进制之间的转换  下面是示例代码,我们直接通过JDK工具库中的方法实现的,如下: public static Integer valueOf(String s, int radix ...

  4. Java基础知识强化03:Java中的堆与栈

    1.在JVM中,内存分为两个部分,Stack(栈)和Heap(堆),这里,我们从JVM的内存管理原理的角度来认识Stack和Heap,并通过这些原理认清Java中静态方法和静态属性的问题. 一般,JV ...

  5. Java基础知识强化14:Java死亡竞赛题目解析

      一个小型网站上发布了一个称为Java“死亡竞赛”的新项目.测验发布后,超过20000位开发者参加了测验.网站以20道关于Java的多选题为主.我们得到了众多开发者的测验统计数据,今天,我们非常乐意 ...

  6. Java基础知识强化103:Java常量池理解与总结

    一.相关概念 1. 什么是常量 用final修饰的成员变量表示常量,值一旦给定就无法改变! final修饰的变量有三种:静态变量.实例变量和局部变量,分别表示三种类型的常量. 2. Class文件中的 ...

  7. Java基础知识强化101:Java 中的 String对象真的不可变吗 ?

    1. 什么是不可变对象?       众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对 ...

  8. Java基础知识强化13:Java中单例模式案例使用(懒汉式)

    1.古往今来历史上皇帝通常只有一人.为了保证其唯一性,古人采用增加"防伪标识"的办法,如玉玺.更为简单的办法就是限制皇帝的创建.本案例中就是使用单例模式从而保证皇帝的唯一性.实例运 ...

  9. Java基础知识强化12:Java中运用数组的四种排序方法

    1.使用JavaApi文档中的Arrays类中的sort()进行快速排序 首先我们直接看代码如下: package himi.text; import java.util.Arrays; public ...

随机推荐

  1. Laravel框架——分页

    第一种:查询时实现分页(不能使用groupBy) $users = App\User::paginate(15); or $users = User::where('votes', '>', 1 ...

  2. FutureTask源码解读

    import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.ut ...

  3. 搭建Tornado+Nginx

    Tornado一个高效的异步非阻塞式的实时Web服务器,是Facebook旗下的 FriendFeed 网站开源Web服务器版本.但是它内置的HTTP服务器功能有限,不能在生产环境下使用. 在 Fri ...

  4. xfire实现webservice客户端之测试关注点

    日前的工作接触到很多系统间的Webservice调用,这里想谈谈基于spring+xfire实现的webservice的客户端踩过的一些坑,需要测试关注的点. xFire的配置项 在spring中实现 ...

  5. 关于如何设置reduce的个数

    在默认情况下,一个MapReduce Job如果不设置Reducer的个数,那么Reducer的个数为1.具体,可以通过JobConf.setNumReduceTasks(int numOfReduc ...

  6. oracle技巧-持续更新

    1. 登录oracle数据库,执行select status from v$encryption_wallet,如果返回OPEN,表示钱夹已自动打开. 2.

  7. 设计模式之(三)Proxy模式

    今天学习Proxy模式.代理模式是在对已有对象操作困难或者不太方便时,选择用代理的方式对对象进行访问.Proxy实现的方法必须和被代理对象一致. 举一个简单的例子, 有一个Math类实现了IMath接 ...

  8. 14.6.7 Configuring the Number of Background InnoDB IO Threads 配置InnoDB IO Threads的数量

    14.6.7 Configuring the Number of Background InnoDB IO Threads 配置InnoDB IO Threads的数量 InnoDB 使用后台线程来服 ...

  9. python string 文本常量和模版

        最近在看python标准库这本书,第一感觉非常厚,第二感觉,里面有很多原来不知道的东西,现在记下来跟大家分享一下.     string类是python中最常用的文本处理工具,在python的 ...

  10. 用GPUImage开启相机并且开启滤镜效果

    GPUImage提供了GPUImageVideoCamera这么一个类,它的对象能够调用摄像头,并且加上滤镜的效果.     //init VideoCamera     //这里的两个参数可以设定拍 ...