chapter07_取消与关闭_1_任务取消

  • (1) 可取消的

    如果外部代码可以在某个操作正常完成之间就__将其置入完成状态__, 那么这个操作就是__可取消的__

    (2) 取消某个操作的原因

    1° 点击取消按钮

    2° 时间限制

    3° 并行计算, 某一部分已经得到运行结果, 其他部分需要取消

    4° 出现错误(例如磁盘已满)

    5° 服务关闭

  • 简单的取消策略

    示例

      @ThreadSafe
      public class PrimeGenerator implements Runnable {
    
          private static ExecutorService exec = Executors.newCachedThreadPool();
    
          @GuardedBy("this")
          private final List<BigInteger> primes = new ArrayList<BigInteger>();
          private volatile boolean cancelled;
    
          public void run() {
    
              BigInteger p = BigInteger.ONE;
    
              while (!cancelled) {
    
                  p = p.nextProbablePrime();
          
                  synchronized (this) {
                      primes.add(p);
                  }
              }
          }
    
          public void cancel() {
    
              cancelled = true;
          }
    
          public synchronized List<BigInteger> get() {
    
              return new ArrayList<BigInteger>(primes);
          }
    
          private static List<BigInteger> aSecondOfPrimes() throws InterruptedException {
    
              PrimeGenerator generator = new PrimeGenerator();
              exec.execute(generator);
    
              try {
                  SECONDS.sleep(1);
              } finally {
                  generator.cancel();
              }
    
              return generator.get();
          }
    
          public static void main(String[] args) {
    
              List<BigInteger> result = null;
    
              try {
                  result = aSecondOfPrimes();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
    
              //exec.shutdown();
    
              System.out.println(result);
          }
      }
    

    这个示例中使用一个volatile的变量作为判断循环终止的标志, 实现了外部可取消;

    但是, 这种方式__不太靠谱__, 因为它__必须等到下次循环开始__时才能退出: 如果本次循环有很耗时的操作, 那么需要等待很久才能到下次循环; 如果本次循环中某处存在阻塞, 那很可能永远取消不了

  • 线程中断

    (1) 线程中断是一种协作机制

    线程可以通过这种机制来通知另一个线程, 告诉它在合适的或者可能的情况下停止当前工作, 并转而执行其他的工作

    (2) 中断是实现取消的最合理方式

    (3) 如果在取消之外的其他操作使用中断, 那么都是不合适的

    (4) Thread类里面和中断相关的方法

      public void interrupt();
    
      public boolean isInterrupted();
    
      public static boolean interrupted();
    

    每个线程都有一个boolean的中断状态. 当中断线程时, 这个线程的中断状态将被置为true;

    interrupt()方法可以中断目标线程;

    isInterrupted()返回目标线程的中断状态;

    interrupted()是一个__静态__方法, 它可以__清除当前线程的中断状态__, 并返回它之前的值;

    (5) 阻塞的库方法, 例如Thread.sleep(), Object.wait()等, 都会检查线程是否被中断: 如果被中断, 则清除中断状态, 抛出InterruptedException并提前返回

    (6) 当线程在非阻塞状态下中断时, 它的中断状态将被设置; 如果不触发InterruptedException, 那么中断状态将一直保持, 直到明确的清除中断状态

    (7) 对中断操作的__正确理解__是:

    它不会真正的中断一个正在运行的线程, 而只是发出中断请求, 然后由线程在下一个合适的时刻中断自己

    (8) 调用interrupted()要小心, 因为它会清除中断状态

    要么处理这个中断, 要么抛出InterruptedException, 要么再次调用Thread.currentThread().interrupt()恢复中断状态

  • 中断策略

    (1) 中断策略规定了: 发现中断请求时, 应该做哪些工作

    (2) 任务通常不会只在自己拥有的线程中执行, 因此对于非线程所有者的代码来说, 应该小心保存中断状态, 使得真正拥有线程的代码对中断做出响应

    因此, 大多数可阻塞的库函数都是抛出InterruptedException作为中断响应, 因为它们经常会在某个不是由自己拥有的线程中运行

    (3) 线程应该只能由所有者中断

  • 响应中断

    (1) 当catch到InterruptedException后, 此时表示当前线程的中断状态的标志位__已经被清除__

      ...
    
      try {
          queue.put(p = p.nextProbablePrime());
      } catch (InterruptedException e) {
    
          if (Thread.currentThread().isInterrupted()) {
              System.out.println(true);
          } else {
              System.out.println(false);
          }             
      }
    

    所以, 如果只catch而不做任何处理的话, 相当于__屏蔽中断__, 丢失了线程被中断的信息

    (2) 处理InterruptedException的策略

    1° 直接传递InterruptedException异常

    对于非Runnable实现类的run()方法, 都可以直接将捕捉到的异常传递出去或者直接在方法上抛出

    2° 恢复中断状态

    对于不想或无法传递InterruptedException的情况, 可以再次调用interrupt()来恢复中断状态, 从而让调用栈中的上层代码能够进行处理

    示例

      class MyRunnable implements Runnable {
    
          @Override
          public void run() {
    
              try {
                  inner();
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
              }
          }
    
          private void inner() throws InterruptedException {
    
              System.out.println("Enter inner");
    
              Thread.sleep(1000);
          }
      }
    
      class TestMyRunnable {
    
          public static void main(String[] args) {
    
              Thread thread = new Thread(new MyRunnable());
    
              thread.start();
    
              thread.interrupt();
    
              if (thread.isInterrupted()) {
                  System.out.println("haha, thread is interrupted");
              }
          }
      }
    

    示例中, inner()方法直接将Thread.sleep()方法中的InterruptedException异常向外抛出, 由于run()方法无法抛出异常, 所以它调用Thread.currentThread().interrupt();恢复中断状态, 线程的中断状态被主线程捕捉到, 输出"haha, thread is interrupted"信息

    (3) 只有实现了线程中断策略的代码, 才可以屏蔽中断请求, 常规的任务和库代码都不应该屏蔽中断请求

  • 通过Future实现取消

    (1) 示例

      public class TimedRun {
    
          private static final ExecutorService taskExec = Executors.newCachedThreadPool();
    
          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) {
                  // task will be cancelled below
              } catch (ExecutionException e) {
                  // exception thrown in task; rethrow
              } finally {
                  // Harmless if task already completed
    
                  task.cancel(true); // interrupt if running
              }
          }
      }
    

    submit()方法将返回一个Future对象, Future对象有一个cancel(boolean)方法: 如果参数为true代表__向正在运行的任务传入中断信号__; 如果参数为false, 代表正在运行的任务继续运行, 但是不再开启新的任务

    注意, 正如"0 绪论.md"所说, Java没有机制保证某个线程停止, 因此cancel只是向某个任务线程传递中断信号, 如果它不能响应中断, 那么这个线程无论如何都停止不了

  • 处理不可中断的阻塞

    (1) 有的阻塞是不响应中断的, 导致它们在想让它们停下来的时候停不掉

    例如 java.net.Socket套接字的read和write操作; 等待获得内置锁而阻塞

    (2) 对于Socket的处理方法

    虽然read和write不能响应中断, 但是__可以通过让套接字关闭的方式, 这时read和write操作就会抛出IOException异常从而响应中断__

    示例

      public class ReaderThread extends Thread {
    
          private static final int BUFFER_SIZE = 512;
    
          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[BUFFER_SIZE];
    
                  while (true) {
                      int count = in.read(buf);
                      if (count < 0) {
                          break;
                      } else if (count > 0) {
                          processBuffer(buf, count);
                      }
                  }
    
              } catch (IOException e) { 
                  /* Allow thread to exit */
              }
          }
    
          private void processBuffer(byte[] buf, int count) {
          }
      }
    

    其他涉及连接操作的阻塞都可以通过类似的在中断时关闭底层连接的方式抛出异常,从而提前终止线程

    (3) 对内置锁synchronized阻塞的处理

    没办法, 但是可以考虑换成Lock类代替内置锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值