关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » JAVA »深入分析Java线程中断机制

    深入分析Java线程中断机制

    2015-08-01 00:00:00 出处:segmentfault
    分享

    Thread.interrupt真的能中断线程吗

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

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

    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
      {
    
        @Override
        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 的方法反映出来

    处理线程中断的常用方法

    设置取消标记

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

    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;
    
        @Override
        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 ...1438396915809
    我已经结束了...1438396922809

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

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

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

    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...1438398800110
    我已经结束了...1438398800110
    end interrupt ...1438398800110

    这次是立马中断的,但是这种方法是由局限性的,这种方法仅仅对于会抛出InterruptedException 异常的任务时有效的,比如java中的sleep、wait 等方法,对于不会抛出这种异常的任务其效果其实和第一种方法是一样的,都会有延迟性,这个例子中还有一个非常重要的地方就是cache语句中,我们调用了Thread.currentThread().interrupt() 我们把这句代码去掉,运行你会发现这个线程无法终止,因为在抛出InterruptedException 的同时,线程的中断标志被清除了,所以在while语句中判断当前线程是否中断时,返回的是false.针对InterruptedException 异常,我想说的是:一定不能再catch语句块中什么也不干,如果你实在不想处理,你可以将异常抛出来,让调用抛异常的方法也成为一个可以抛出InterruptedException 的方法,如果自己要捕获此异常,那么最好在cache语句中调用 Thread.currentThread().interrupt(); 方法来让高层只要中断请求并处理该中断。

    对于上述两种方法都有其局限性,第一种方法只能处理那种工作量不大,会频繁检查循环标志的任务,对于第二种方法适合用于抛出InterruptedException的代码。也就是说第一种和第二种方法支持的是支持中断的线程任务,那么不支持中断的线程任务该怎么做呢。

    例如 如果一个线程由于同步进行I/O操作导致阻塞,中断请求不会抛出InterruptedException ,我们该如何中断此线程呢。

    处理不支持中断的线程中断的常用方法

    改写线程的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 方法实现,那么对于使用线程池的任务该怎么中断呢。

    改写线程池的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方法异常,从而终止线程任务。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    IT头条

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    严重缩水!NVIDIA将推中国特供RTX 5090 DD:只剩24GB显存

    00:17

    无线路由大厂 TP-Link突然大裁员:补偿N+3

    02:39

    Meta 千万美金招募AI高级人才

    00:22

    技术热点

    Spring基础知识汇总 Java开发必看

    SQL Server索引与其性能的描述

    SQL Server 2008数据格式修改时应注意什么?

    如何禁止windows 7网络搜索驱动?windows 7禁止网络搜索驱动的方

    SQL Server系统表中的sysconfigures表

    如何恢复windows 7、windows 8图片预览功能详细图解

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。