zoukankan      html  css  js  c++  java
  • Java基础知识强化16:深入分析Java线程中断机制

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

  • 相关阅读:
    ArduinoYun教程之ArduinoYun硬件介绍
    Nmap扫描教程之基础扫描详解
    Nmap扫描教程之Nmap基础知识
    Nmap扫描教程之DNS服务类
    Nmap扫描教程之网络基础服务DHCP服务类
    Xamarin iOS教程之自定义视图
    Xamarin iOS教程之警告视图
    Xamarin iOS教程之页面控件
    使用光学鼠标传感器实现旋转(或线性)测量(转)
    基于STM32的uCOS-II移植详解
  • 原文地址:https://www.cnblogs.com/hebao0514/p/4781380.html
Copyright © 2011-2022 走看看