Java并发编程实战(学习笔记六 第七章 取消与关闭 上)

通过使用FutureTask和Executor框架,可以帮助我们构建可取消的任务和服务。

7.1 任务取消(Task Cancellation)

如果外部代码能在某个操作正常之前将其置入“完成”状态,那么这个操作就可以称为可取消的(Cancellable)。

取消某个操作的原因有很多:
用户请求取消
用户点击图形界面程序中的“取消”按钮,或者通过管理结构来发出取消请求,例如JMX(Java Management Extensions)

有时间限制的操作
例如,某个应用程序需要在有限时间内搜索问题空间,并在这个时间内选择最佳的解决方法。当计数器超时时,需要取消所有正在搜索的任务。

应用程序事件
例如,应用程序对某个问题空间进行分解并搜索,从而使不捅的任务可以搜索问题空间中的不同区域。当其中一个任务找到了解决方案时,所有其他仍在搜索额任务都将被取消。

错误
网页爬虫程序搜索相关的页面,并将页面或摘要数据保存到硬盘。当一个爬虫任务发生错误时(例如,磁盘空间已满),那么所有搜索任务都会取消,此时可能会记录它们的当前状态,以便稍后重新启动。

关闭
当一个程序或服务关闭时,必须对正在处理和等待处理的工作执行某种操作。在平缓的关闭过程中,当前正在执行的任务将继续执行直到完成,而在立即关闭过程中,当前的任务则可能取消。

什么时候会发生中断:

点击某个桌面应用中的取消按钮时;
某个操作超过了一定的执行时间限制需要中止时;
多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
一组线程中的一个或多个出现错误导致整组都无法继续时;
当一个应用或服务需要停止时。

我们在使用电脑过程中都会发现,当我们点击取消按钮的时候,大多数情况下并不是立刻结束,而是会提示正在取消并且会持续一段时间,这个就是在进行中断处理。

Java中没有一种安全的抢占式方法来停止线程,也就没有安全的抢占式方法来停止任务。只有一些协作式的机制,使请求取消的任务和代码都遵循一种协商好的协议。

其中一种协作机制能设置某个“已请求取消(Cancellation Requested)标志”,而任务将定期地查看这个标志。如果设置了这个标志,那么任务将提前结束。

PrimeGenerator使用了这项技术,其中的PrimeGenerator持续地枚举素数(Prime),直到它被取消。cancel方法将设置cancelled标志,并且在

//        7-1      使用Volatile类型的域来保存取消状态  
public class PrimeGenerator implements Runnable{
    private final List<BigInteger> primes=
            new ArrayList<BigInteger>();
    private volatile boolean cancelled;//volatile变量能确保可见性 禁止指令重排序

    public void run(){
        BigInteger p=BigInteger.ONE;//常量
        while(!cancelled){  //任务将定期地查看这个标志。如果设置了这个标志,那么任务将提前结束。(即如果cancelled不为true则一直运行)
            p=p.nextProbablePrime();//返回一个比当前大的
            synchronized (this) { //确保不会被添加多次
                primes.add(p);
            }
        }
    }

    public void cancel(){
        cancelled=true;      //将“已请求取消标志”取为true
    }

    public synchronized List<BigInteger> get(){
        return new ArrayList<BigInteger>(primes);
    }
}

aSecondOfPrimes让素数生成器运行1秒钟后取消。素数生成器通常不会在刚好一秒后停止,因为在请求取消的时刻和run方法中循环执行下一次检查之间可能存在延迟。cancel方法由finally块调用,确保即使在调用sleep时被中断也能取消素数生成器的执行。如果cancel没有被调用,那么搜索素数的线程将永远运行下去,不断消耗CPU的时钟周期,并使得JVM不能正常退出。


//            7-2      一个仅运行一秒的素数生成器
  static List<BigInteger> aSecondOfPrimes() throws InterruptedException{
        PrimeGenerator generator=new PrimeGenerator();
        new Thread(generator).start();
        try{
            SECONDS.sleep(1); //延迟一秒才执行cancel
        }finally{
            generator.cancel();
        }
        return generator.get();

    }

一个可取消ed任务必须拥有取消策略(Cancellation Policy)

PrimeGenerator使用了一种简单的取消策略:客户代码通过调用cancel来请求取消,PrimeGenerator在每次搜索素数之前都首先检查是否存在取消请求,如果存在则退出。

7.1.1 中断(Interruption)

PrimeGenerator中的取消机制最终会使得搜索素数的任务退出,但在退出过程中需要花费一定的时间。然而, 如果使用这种方法的任务调用了一个阻塞方法,例如BlockingQueue.put,那么可能会产生一个更严重的问题——任务可能永远不会检查取消标志,因此永远不会结束。

下面的BrokenPrimeGenerator说明了这个问题。生产者线程生成素数,并将它们放入一个阻塞队列。如果生成者的速度超过了消费者的处理速度, 队列将被填满,put方法也会阻塞。当生产者在put方法中阻塞时,如果消费者希望取消生产者任务,它可以调用cancel方法来设置cancelled标志,但此时生产者却永远不能检查这个标志,因为它无法从阻塞的put方法中恢复过来(因为消费者此时已经停止从队列中取出素数,所以put方法将一直保持阻塞状态。)

//             7-3    不可靠的取消操作吧生成者置于阻塞的操作中(不要这么做)
public class BrokenPrimeProducer extends Thread {
       private final BlockingQueue<BigInteger> queue;
       private volatile boolean cancelled=false;

      public BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue=queue;
      }

      public void run(){
          try{
              BigInteger p=BigInteger.ONE;
              while(!cancelled){
                  queue.put(p=p.nextProbablePrime());
              }
          }catch (InterruptedException consumed) {

        }
      }
}

void consumePrimes() throws InterruptedException{
    BlockingQueue<BigInteger> primes=new ArrayBlockingQueue<>(MAX_PRIORITY);
    BrokenPrimeProducer producer=new BrokenPrimeProducer(primes);
    producer.start();
    try{
        while(needMorePrimes())              //如果需要继续消费
            consume(primes.take());
    }finally{              
        //它可以调用cancel方法来设置cancelled标志,但此时生产者却永远不能检查这个标志,因为它无法从阻塞的put方法中恢复过来(因为消费者此时已经停止从队列中取出素数,所以put方法将一直保持阻塞状态。)
        producer.cancel();
    }
}

一种特殊的方法是支持中断。线程中断是一种协作机制,线程可以通过这个机制来通知另一个线程,告诉它在合适的或可能的情况下停止当前工作,并转而执行其他的工作。

如果在取消之外的其他操作中使用了中断,那么都是不合适的,并且很难支撑起更大应用。

每个线程都有一个boolean类型的中断状态。当中断线程时,这个线程的中断状态将被设置为true。
在Thread中包含了中断线程以及查询线程中断状态的方法,如下:
interrupt方法能中断目标线程
isInterrupt方法能返回目标线程的中断状态
静态的interrupted方法将清除当前线程的中断状态,并返回它之前的值,这也是清除中断状态的唯一方法。

//     7-4      Thread中的中断方法
public class Thread {
     //interrupt方法能中断目标线程
    public void interrupt() { ... }
    //isInterrupt方法能返回目标线程的中断状态
    public boolean isInterrupted() { ... } 
    //静态的interrupted方法将清除当前线程的中断状态,并返回它之前的值,这也是清除中断状态的唯一方法。
    public static boolean interrupted() { ... }
...
}

阻塞库方法,例如Thread.sleep和Object.wait,都会检查线程何时中断,并且在发现中断时提前返回。它们在响应中断时的操作包括,清除中断状态,抛出InterruptedException,表示阻塞操作由于中断而提前结束。

当线程在非阻塞状态下中断时,它的中断状态将被设置,然后根据被取消的操作来检查中断状态以及发生了中断,通过这样的方法,中断操作将变得“有黏性”——如果不能触发InterruptedException,那么中断状态将一直保持,直到明确地清除中断状态。

调用Interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。

对中断操作的正确理解是:它并不会真正中断一个正在运行的线程,而只是发出中断骑牛,然后在线程下一个适合的时刻中断自己。(这个时刻被称为取消点)。
有些方法,如wait,sleep和join等,将严格地处理这种请求,当它们收到中断请求或者在开始执行时发现某个已被设置好的中断状态时,将抛出一个异常。

在使用静态的interrupted时应该小心,因为它会清除当前线程的中断状态。如果在调用interrupted时返回了true,那么除非你想屏蔽这个中断,否则你必须对它进行处理——可以抛出InterruptedException,或者通过再次调用interrupt来恢复中断状态。

通常,中断是实现取消的最合适方法。

在每次迭代循环中,有两个位置可以检测出中断:在阻塞的put方法中调用,以及在循环开始处查询中断状态。
由于调用了阻塞的put方法,因此这里不一定需要显式的检测,但执行检测会使PrimeProducer对中断具有更高的响应性,因为它是在启动寻找素数任务之前检测中断的,而不是在任务完成后。
如果可中断的阻塞方法的调用频率不高,不足以获得足够的响应性,那么显式地检测中断状态能起到一定的帮助作用。

//       7-5 通过中断来取消
public class PrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;

    PrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            //在每次迭代循环中,有两个位置可以检测出中断:在阻塞的put方法中调用,以及在循环开始处查询中断状态。
            while (!Thread.currentThread().isInterrupted())
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
            /* 允许线程退出 */
        }
    }

    public void cancel() {
        interrupt();
    }
}

7.1.2 中断策略

最合理的中断策略是某个形式的线程级(Thread-Level)取消操作或服务级(Service-Level)取消操作:尽快退出,在必要时进行清理,通知某个所有者该线程已经退出。
此外还可以奖励其他的中断策略,例如暂停服务或重新开始服务,但对于那些包含非标准中断策略的线程或线程池,只能用于能知道这些策略的任务中。

一个中断请求可以有一个或多个接受者——中断线程池中的某个工作者线程,同时意味者“取消当前任务”和“关闭工作者线程”。

任务不会在其自己拥有的线程中执行,而是在某个服务(例如线程池)拥有的线程中执行。
对于非线程所有者的代码来说(例如,对于线程池而言,任何在线程池实现以外的代码),应该小心地保存中断状态,这样拥有线程的代码才能对中断做出响应,即使“非所有者”代码也可以做出响应。

这就是为什么大多数可阻塞的库函数都只是抛出InterruptedException作为中断响应。它们永远不会在某个由自己拥有的线程中运行,因此它们为任务或库代码实现了最合理的取消策略:尽快退出流程,并把中断信息传递给调用者,从而使调用栈中的上层代码可以采取进一步的操作。

当检查到中断请求时,任务并不需要放弃所有的操作——它可以推迟处理中断请求,并直到某个更合适的时刻。因此需要记住中断请求,并在完成当前任务后抛出InterruptedException或表示已收到中断状态,这项技术能够确保在更新过程中发生中断时,数据结构不会被破坏。

任务不应该对执行该任务的线程的中断策略做出任何假设,除非该任务被专门设计为服务中运行,并且在这些服务中抱哈特定的中断策略。
无论任务将中断视为取消,还是其他某个中断响应操作,都应该小心地保存执行线程的中断状态。
如果除了将InterruptedException传递给调用者外需要执行其他操作,那么应该在捕获InterruptedException之后恢复中断状态:
Thread.currentThread.interrupt();

执行取消操作的代码也不应该对线程的中断策略做出假设,线程应该只能由所有者中断,所有者可以将线程的中断策略信息想封装到某个合适的取消机制中,例如shutdown方法。

7.1.3 响应中断(Responding to Interruption)

在5.4中,当调用可中断的阻塞函数时,例如Thread.sleep或Blocking.put等,有两种实用策略可用于处理InterruptedException:
传递异常(可能在某个特定于任务额清除操作之后),从而使你的方法也称为可中断的阻塞方法。

恢复中断状态,从而使调用栈上的上层代码能够对其进行处理。

传递InterruptedException如下:

//       7-6  将InterruptedException传递给调用者
BlockingQueue<Task> queue;
...
public Task getNextTask() throws InterruptedException {
  return queue.take();
}

保存中断请求,一种标注你的方法就是通过再次调用interrupt来恢复中断状态

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

对于一些不支持取消但仍可以调用可中断阻塞方法的操作,它们必须在循环中调用这些方法,并在发现中断后重新尝试。在这种情况下,它们应该在本地保存中断状态,并在返回前恢复状态而不是在捕获InterruptedException时恢复状态,如

//      7-7   不可取消的任务在退出前恢复中断
public Task getNextTask(BlockingQueue<Taskgt; queue) {
   boolean interrupted = false;
   try {
       while (true) {
           try {
               return queue.take();
           } catch (InterruptedException e) {
                 interrupted = true;
                // 重新尝试
           }
       }
   } finally {
        if (interrupted)
            Thread.currentThread().interrupt();
     }
}

如果代码不会调用可中断的阻塞方法,那么仍然可以铜鼓偶在任务代码中轮询当前线程的中断状态爱响应中断。

中断可以用来获得线程的注意,并且由中断线程保存的信息,可以为中断的线程提供进一步的指示(当访问这些信息时,要确保使用同步)。

例如,当一个由ThreadPoolExecutor拥有的工作者线程检测到中断时,它会检测线程池是否正在关闭。如果是,它会在结束之前执行一些线程池清理工作,否则它可能创建一个新线程将线程池恢复到合理的规模

7.1.4 示例:计时运行

上面的aSecondOfPrimes方法将启动一个PrimeGenerator,并在1秒钟后中断,尽管PrimeGenerator可能需要超过1秒的时间才能停止,但它最终会发现中断,然后停止,并使线程结束。执行任务的另一个方面是,你希望知道在任务执行过程中是否会抛出异常。如果PrimeGenerator在指定时限内抛出了一个未检查的异常,那么这个异常可能被忽略,因为素数生成器在另一个独立的线程总运行,而这个线程不会显式处理异常。

timedRun1给出了在指定时间内运行一个任意的Runnable的实例。它在调用线程中运行任务,并安排了一个取消任务,在运行指定的时间间隔后中断它。这解决了从任务中抛出未检查异常的问题,因为该异常会被timeRun1的调用者捕获

//        7-8    在外部线程中安排中断(不要这么做)
public class TimedRun1 {
    private static final ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(1);
  //r是任务
    public static void timedRun(Runnable r,
                                long timeout, TimeUnit unit) {
        //返回一个并发执行的线程的引用
        final Thread taskThread = Thread.currentThread();
        //它在调用线程中运行任务,并安排了一个取消任务,在运行指定的时间间隔后中断它。
        cancelExec.schedule(new Runnable() {
            public void run() {
                taskThread.interrupt();
            }
        }, timeout, unit);
        //运行任务
        r.run();
    }
}

在中断线程之前,应该了解它的中断策略。由于timeRun1可以从任意一个线程中调用,因此它无法知道这个调用线程的中断策略。如果任务在超时之前可以完成,那么中断timeRun1所在线程的取消任务将在timeRun1返回到调用者之后启动。

而且,如果任务不响应中断,那么timedRun1会在任务结束时才返回,此时可能已经超过了指定的时限。

下面的timeRun2解决了aSecondOfPrimes的异常处理问题以及之前解决方案中的问题。
执行任务的线程拥有自己的执行策略,即使任务不响应中断,限时运行的方法仍然能返回到它的调用者。在启动任务线程之后,timeRun将执行一个限时的join方法。

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

在join返回后,它将检查任务中是否有异常抛出,如果有的话,则会在调用timedRun2的线程中再次抛出应该异常。

//           7-9  在专门的线程中中断任务     
public class timeRun2 {
    private static final ScheduledExecutorService cancelExec = newScheduledThreadPool(1);

    public static void timedRun(final Runnable r,
                                long timeout, TimeUnit unit)
            throws InterruptedException {
        class RethrowableTask implements Runnable {
            private volatile Throwable t; //所有error和exception的超类

            public void run() {
                try {
                    r.run();
                } catch (Throwable t) {
                    this.t = t;
                }
            }

            void rethrow() {
                if (t != null)
                    throw LaunderThrowable.launderThrowable(t);
            }
        }
//执行任务的线程拥有自己的执行策略,即使任务不响应中断,限时运行的方法仍然能返回到它的调用者。
        RethrowableTask task = new RethrowableTask();
        final Thread taskThread = new Thread(task);
        taskThread.start();
        cancelExec.schedule(new Runnable() {
            public void run() {
                taskThread.interrupt();
            }
        }, timeout, unit);
        taskThread.join(unit.toMillis(timeout));//等待任务线程的消亡,即等它运行完毕
        //在join返回后,它将检查任务中是否有异常抛出,如果有的话,则会在调用timedRun2的线程中再次抛出应该异常。
        task.rethrow();
    }
}

这个实例解决了前面的问题,但由于它依赖于一个限时的join,因此存在这join的不足:无法知道执行控制是因为线程正常退出还是因为join超时而返回。

7.1.5 通过Future来实现取消

我们使用了一个抽象机制Future来管理任务的生命周期,处理异常,以及实现取消。

ExecutorService.submit将返回一个Future来描述任务。
Future拥有一个cancel方法,该方法带有一个boolean类型的参数mayInterruptIfRunning,表示取消操作是否成功(这只是表示任务是否能够接收中断,而不是表示任务是否能检测并处理中断)。
如果mayInterruptIfRunning为true并且任务当前正在某个线程中运行,那么这个线程能被中断。如果为false,那么意味着“若任务还没有启动,就不要运行它”。

除非你清除线程的中断策略,否则不要中断线程。
执行任务的线程是由标准的Executor创建的,它实现了一种中断策略使得任务可以通过中断被取消,所有如果任务在标准Executor中运行,并通过它们的Future来取消任务,那么可以设置mayInterruptIfRunning。当尝试取消某个任务时,不宜直接中断线程池,因为你并不知道当中断请求时正在运行什么任务——只能通过任务的Future来实现取消。

//          7-10     通过Future来取消任务
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) {
            // 接下来任务会被取消
        } catch (ExecutionException e) {
            // 如果在任务中抛出了异常,那么重新抛出该异常
            throw launderThrowable(e.getCause());
        } finally {
            // 如果任务已经结束,那么执行取消操作也不会有任何影响
            task.cancel(true); // 如果任务正在进行,那么将被中断
        }
    }
}

当Future.get抛出InterruptedException或TimeoutException,如果你不需要知道结果,那么就可以调用Future.cancel来取消任务。

7.1.6 处理不可中断的阻塞 (Dealing with Non-interruptible Blocking)

并非所有的可阻塞方法或者阻塞机制都能响应中断:如果一个线程由于执行同步的Socket I/O或的等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有任何其他作用。对于那些由于执行不可中断操作而阻塞的线程,可以使用类似与中断的手段来停止这些线程。

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要 通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。

Java.io包中的同步Socket I/O。在服务器应用程序中,最常见的阻塞I/O形式就是对套接字进行读取和写入。虽然InputStream和OutputStream中的read和write等方法不会响应中断,但通过关闭底层的socket(套接字),可以使得由于执行read和write等方法而被阻塞的线程抛出一个SocketException。

Java.io包中的同步I/O。当中断一个正在InterruptibleChannel上等待的线程时,将抛出ClosedByInterruptException并关闭链路(channel)(这会使得其他在这条链路上阻塞的线程同样抛出ClosedByInterruptException)。当关闭一个InterruptibleChannel时,将导致所有在链路操作上阻塞的线程都抛出AsynchronousCloseException。大多数标准的Channel都实现(implements)了InterruptibleChannel。

Selector的异步I/O。如果一个线程在调用Selector.select方法(在java.nio.channels中)时阻塞了,那么调用close和wakeup方法会使线程抛出ClosedByInterruptException并提前返回。

获取某个锁。如果一个线程由于等待某个内置锁而阻塞,那么将无法响应中断,因为线程认为它肯定会得到锁,所有将不会理会中断请求。但是,在Lock类提供了lockInterruptibly方法,该方法允许在等待一个锁的同时仍能响应中断。

ReaderThread给出了如何封装非标准的取消操作。ReaderThread管理了一个套接字链接,它用同步的方式从该套接字中读取数据,并将接收到的数据传递给processBuffer。为了结束某个用户的连接或者关闭服务器,ReaderThread改写了interrupt方法,使其既能处理标准的中断,也能关闭底层的套接字。因此,无论ReaderThread线程是在read方法中阻塞还是在某个可中断的阻塞方法中阻塞,都可以被中断并停止执行当前的工作。

//               7-11 通过改写interrupt方法将非标准的取消操作封装在Thread中
public class ReaderThread extends Thread{
    private static final int BUFSZ = 512;
    private final Socket socket;
    private final InputStream in;

    public ReaderThread(Socket socket) throws IOException{
        this.socket=socket;
        this.in=socket.getInputStream();
    }
    //改写了interrupt方法,使其既能处理标准的中断,也能关闭底层的套接字
    public void interrupt(){
        try{
            socket.close();
        }catch (IOException ignored) {      
        }finally{
            //调用继承的Thread中的方法处理标准的中断
            super.interrupt();
        }
    }

    public void run(){
        try{
            byte[] buf=new byte[BUFSZ];
            while(true){       //用同步的方式从该套接字中读取数据
                int count=in.read(buf);
                if(count<0)
                    break;
                else if(count>0)
                    processBuffer(buf,count); //并将接收到的数据传递给processBuffer
            }
        }catch (IOException e) {
            // 允许线程退出       
        }
    }

    public void processBuffer(byte[] buf, int count) {
    }
}

7.1.7 采用newTaskFor来封装非标准的取消
我们可以通过newTaskFor来进一步优化ReaderThread 中封装的非标准取消的技术。

当把一个Callable(或Runnable)提交给ExecutorService时,sunmit方法会返回一个Future。我们可以通过这个Future来取消任务。newTaskFor是一个工厂方法,它将创建Future来代表任务。newTaskFor还能返回一个RunnableFuture接口,该接口继承(扩展)了Future和Runnable(并由FutureTask实现)。

通过定制表示任务的Future可以改变Future.cancel的行为。例如,定制的取消代码可以实现日志记录或者收集取消操作的统计信息,以及一些不响应中断的操作。通过改写interrupt方法,ReaderThread可以取消基于套接字的线程。通过,通过改写任务Future,cancel也可以实现类似的功能。

CancellableTask定义了一个CancellableTask接口,该接口扩展了Callable,并增加了一个cancel方法和newTask方法来构造RunnableFuture。

//   7-12     通过newTaskFor将非标准的取消操作封装在一个任务中
//CancellableTask定义了一个CancellableTask接口,该接口扩展了Callable,并增加了一个cancel方法和newTask方法来构造RunnableFuture。
public interface CancellableTask<T> extends Callable<T> {
       void cancel();
       RunnableFuture<T> newTask();
}

CancellingExecutor继承了THReadPoolExecutor,并通过改写newTaskFor使得CancellableTask可以创建自己的Future

//   7-12     通过newTaskFor将非标准的取消操作封装在一个任务中
//CancellingExecutor继承了THReadPoolExecutor,并通过改写newTaskFor使得CancellableTask可以创建自己的Future
@ThreadSafe
class CancellingExecutor extends ThreadPoolExecutor {
    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        if (callable instanceof CancellableTask)
            return ((CancellableTask<T>) callable).newTask();
        else
            return super.newTaskFor(callable);
    }
}

SocketUsingTask实现了CancellableTask,并定义了Future.cancel来关闭套接字和调用super.cancel。如果SocketUsingTask通过其自己的Future来取消,那么底层的套接字将被关闭并且线程将被中断。因此它提高了任务对取消操作的响应性:不仅能够在调用可中断方法的同时确保响应取消操作,而且还能调用可阻塞的套接字I/O方法。

//            7-12 通过newTaskFor将非标准的取消操作封装在一个任务中
//           SocketUsingTask实现了CancellableTask
public abstract class SocketUsingTask <T> implements CancellableTask<T> {
     private Socket socket;

    protected synchronized void setSocket(Socket s) {
        socket = s;
    }
   //定义了Future.cancel来关闭套接字
    public synchronized void cancel() {
        try {
            if (socket != null)
                socket.close();
        } catch (IOException ignored) {
        }
    }
   //如果SocketUsingTask通过其自己的Future来取消,那么底层的套接字将被关闭并且线程将被中断
    public RunnableFuture<T> newTask() {
        return new FutureTask<T>(this) {  //Creates a FutureTask that will, upon running(正在运行), execute the given Callable.
            @SuppressWarnings("finally")
            public boolean cancel(boolean mayInterruptIfRunning) {
                try {
                    SocketUsingTask.this.cancel();
                } finally {  //调用super.cancel
                    return super.cancel(mayInterruptIfRunning);
                }
            }
        };
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值