zoukankan      html  css  js  c++  java
  • JAVA并发体系-1.1-终结任务或线程

    中断-interrupt

    本文讨论的问题是:在某些情况下,任务必须突然的终止。即有时你希望能够终止处于阻塞状态的任务(todo: 关于何时处于堵塞请查看线程和任务中的解释)。如果对于处于阻塞状态的任务,你不能等待其到达代码中可以检查其状态值的某一点,因而决定让它主动地终止,那么你就必须强制这个任务跳出阻塞状态。

    Runnable.run()方法的中间打断任务,与等待任务执行方法到达检查cancel标志的语句,或者到达程序员准备好离开该执行方法的其他一些地方相比,要棘手得多。

    当打断被阻塞的任务时,可能需要清理资源。正因为这一点,在任务的run()方法中间打断,更像是抛出的异常,因此在Java线程中的这种类型的异常中断中用到了异常。虽然,异常从来都不能异步地传递;但是只要在使用对象互斥机制(与 synchronized关键字相对)时使用try- finally惯用法,在指令/方法调用的中间突然中断没有任何危险。如果抛出异常,使用try- finally惯用法这些互斥就会自动被释放。

    Thread上的中断

    Thread类包含 interrupt()方法,可以使用它终止被阻塞的任务。 interrupt()方法将设置线程的中断状态。

    • 调用时刻:如果一个线程已经被阻塞,或者试图执行一个阻塞操作,这时设置这个线程的中断状态将抛出 InterruptedException

      • 中断发生的唯一时刻是在任务要进入到阻塞操作中,或者已经在阻塞操作内部时
    • 调用过程:当另一个a线程在该b线程上调用 interrupt()方法时,a线程将给该b线程设定一个标志,表明该b线程已经被中断。

    • 重置标志位:

      • InterruptedException异常被捕获时(即抛出该异常时)将清理interrupt标志,中断状态将被复位。所以在 catch子句中,在异常被捕获的时候interrupt标志总是为假。
      • 当任务调用 Thread.isInterrupted()检查中断状态时,中断状态将被复位。
      • 如果想要再次检查以了解是否被中断,则可以在调用 Thread.isInterrupted()时将结果存储起来。
    • 调用 interrupt,必须持有 Thread对象。

    Executor上的中断操作

    新的concurrent类库在避免对Thread对象的直接操作,转而尽量通过 Executor来执行所有操作。但是正如上面所说"调用 interrupt,必须持有 Thread对象。",我们需要知道如何通过 Executor操作。(注意:ExecutorService继承了Executor接口)

    • 中断Executor上的所有进程:

      在ExecutorService上调用 shutdownNow(),那么它将发送一个 interrupt()调用给它启动的所有线程。(这么做是有意义的,因为当你完成工程中的某个部分或者整个程序时,通常会希望同时关闭某个特定 Executor的所有任务)

    • 中断Executor上的单个线程:

      通过调用 submit()来启动任务,持有该任务的上下文即Future。持有这种 Future的关键在于你可以在其上调用 cancel(),并因此可以使用它来中断某个特定任务,将true传递给 cancel(),那么它就会拥有在该线程上调用 interrupt以停止这个线程的权限。

      ExecutorService exec = Executors.newCachedThreadPool();
      Future<?> f = exec.submit(new Runnable(){...});
      f.cancel(true); // Interrupts if running
      

    能/不能中断的调用

    interrupt()能够中断的调用:

    • 对 sleep()的调用
    • 任何要求抛出 InterruptedException的调用
      • 例如在ReentrantLock上堵塞的任务
    • 打断被互斥所堵塞的调用

    interrupt()不能中断的调用:

    • 正在试图获取 synchronized锁的任务
    • 试图执行IO操作的任务

    存在不能中断的调用的事实有点令人烦恼,特别是在创建执行IO的任务时。因为这意味着IO具有锁住你的多线程程序的潜在可能。特别是对于基于Web的程序,这更是关乎利害。

    对于这类问题,有一个略显笨拙但是有时确实行之有效的解决方案,即关闭任务在其上发生阻塞的底层资源

    ExecutorService exec = Executors.newCachedThreadPool();
    ServerSocket server = new ServerSocket(8080);
    exec.execute(new IOBlocked(socketInput));
    exec.shutdownNow(); //this will not shutdown
    socketInput.close(); // Releases blocked thread and then the thread will shutdown
    

    一个ReentrantLock上中断的例子

    ReentrantLock上被堵塞的任务被中断的例子如下代码所示,在这个代码中本输出的执行流程为:point 1>> point 2>>point 3>>point 4>>point 5>>point 6>>point 7>>point 8>>point 9

    关于这个例子需要注意:尽管不太可能,但是对t.interrupt()的调用确实可以发生在对blocked.f()的调用之前,进而产生死锁。

    class BlockedMutex {
      private Lock lock = new ReentrantLock();
      public BlockedMutex() {
        // Acquire it right away, to demonstrate interruption
        // of a task blocked on a ReentrantLock:
        lock.lock();
      }
      public void f() {
        try {
          // This will never be available to a second task
          lock.lockInterruptibly(); // Special call  // point 4 
          print("lock acquired in f()");
        } catch(InterruptedException e) {// point  8
          print("Interrupted from lock acquisition in f()");
        }
      }
    }
    
    class Blocked2 implements Runnable {
      BlockedMutex blocked = new BlockedMutex();
      public void run() {
        print("Waiting for f() in BlockedMutex");// point 2 
        blocked.f();// point 3 
        print("Broken out of blocked call"); // point 9 
      }
    }
    
    public class Interrupting2 {
      public static void main(String[] args) throws Exception {
        Thread t = new Thread(new Blocked2());
        t.start();// point 1 
        TimeUnit.SECONDS.sleep(1);// point 5 
        System.out.println("Issuing t.interrupt()");// point 6 
        t.interrupt();// point 7 
      }
    }
    /*Output:
    *Waiting for f() in BlockedMutex
    *Issuing t.interrupt()
    *Interrupted from lock acquisition in f()
    *Broken out of blocked call
    */
    

    中断检查

    检查中断,保证总是可以离开任务体或无限循环

    如果你只能通过在阻塞调用上抛出异常来退出,那么你就无法总是可以离开run()循环。你可以采取如下两种方法来保证退出任务。

    • 可以通过调用 Thread.isInterrupted()来检查中断状态,这不仅可以告诉你interrupt()是否被调用过,而且还可以清除中断状态。

    • 可以经由单一的InterruptedException或单一的成功的 Thread.interrupted()测试来得到这种通知。

    虽然中断状态在上面两种方法下会被清除,但正如上文所说的:如果想要再次检查以了解是否被中断,则可以在调用 Thread.isInterrupted()时将结果存储起来。

    中断任务的示范

    策略:通过学习之后的这个代码示例来保证可以退出任务体或无限循环)在下面这个例子中,你可以在不同地点退出 Blocked3.run():在阻塞的 sleep()调用中,或者在非阻塞的数学计算中。你将看到,如果 interrupt(在注释 point2之后(即在非阻塞的操作过程中)被调用,那么首先循环将结束,然后所有的本地对象将被销毁,最后循环会经由 while语句的顶部退出。但是,如果 interrupt()在 point1和 point2之间(在 while语句之后,但是在阻塞操作 sleep()之前或其过程中)被调用,那么这个任务就会在第一次试图调用阻塞操作之前,经由InterruptedException退出。在这种情况下,在异常被抛出之时唯一被创建出来的 NeedsCleanup对象将被清除,而你也就有了在catch子句中执行其他任何清除工作的机会。

    注意:被设计用来响应 interrupt的类必须建立一种策略,来确保它将保持一致的状态。这通常意味着所有需要清理的对象创建操作的后面,都必须紧跟 try-finally子句,从而使得无论run()循环如何退出,清理都会发生。

    class NeedsCleanup {
      private final int id;
      public NeedsCleanup(int ident) {
        id = ident;
        print("NeedsCleanup " + id);
      }
      public void cleanup() {
        print("Cleaning up " + id);
      }
    }
    
    class Blocked3 implements Runnable {
      private volatile double d = 0.0;
      public void run() {
        try {
          while(!Thread.interrupted()) {
            // point1
            NeedsCleanup n1 = new NeedsCleanup(1);
            // Start try-finally immediately after definition
            // of n1, to guarantee proper cleanup of n1:
            try {
              print("Sleeping");
              TimeUnit.SECONDS.sleep(1);
              // point2
              NeedsCleanup n2 = new NeedsCleanup(2);
              // Guarantee proper cleanup of n2:
              try {
                print("Calculating");
                // A time-consuming, non-blocking operation:
                for(int i = 1; i < 2500000; i++)
                  d = d + (Math.PI + Math.E) / d;
                print("Finished time-consuming operation");
              } finally {
                n2.cleanup();
              }
            } finally {
              n1.cleanup();
            }
          }
          print("Exiting via while() test");
        } catch(InterruptedException e) {
          print("Exiting via InterruptedException");
        }
      }
    }
    
    public class InterruptingIdiom {
      public static void main(String[] args) throws Exception {
        if(args.length != 1) {
          print("usage: java InterruptingIdiom delay-in-mS");
          System.exit(1);
        }
        Thread t = new Thread(new Blocked3());
        t.start();
        TimeUnit.MILLISECONDS.sleep(new Integer(args[0]));
        t.interrupt();
      }
    } /* Output: (Sample)
    NeedsCleanup 1
    Sleeping
    NeedsCleanup 2
    Calculating
    Finished time-consuming operation
    Cleaning up 2
    Cleaning up 1
    NeedsCleanup 1
    Sleeping
    Cleaning up 1
    Exiting via InterruptedException
    *///:~
    
    
  • 相关阅读:
    PLSQL快捷补充代码设置
    VS2012未找到与约束ContractName...匹配的导出
    VS2012下安装NuGet
    360°全景效果展示
    ArcGIS应用——四种计算图斑面积的方法
    ArcGIS应用——使用Python为图斑连续编号及扩展应用
    Shapefile点图层转换为Shapefile线图层
    SharpMap开发教程——图层标注
    SharpMap入门教程
    常用SQL语句集锦
  • 原文地址:https://www.cnblogs.com/cheaptalk/p/12549678.html
Copyright © 2011-2022 走看看