Java并发编程那些事儿(七)——取消及关闭

这是并发编程系列的第七篇文章。 上一篇
介绍了 Executor
框架和线程池相关的内容,这一篇说一下任务的取消及关闭

俗话说的好,知道如何起飞很重要,但是知道如何平稳落地更重要。线程和任务的启动我们已经很清楚了,但是如何做到线程及任务安全的关闭及取消,也是有一定技巧的。

取消任务

大部分场景下,启动的任务都会按照计划正常的运行,然后结束。但是有些场景我们必须提前结束任务。比如

  1. 用户发送了取消请求。
  2. 超过了某个规定的时间
  3. 发生了某种错误。
  4. 应用程序被关闭。

Java
里面取消任务的执行一般有两种方法。

第一种方法,设置一个标志位,任务执行过程会检查该标志位,如果为 true
则结束任务。该方法对于执行阻塞调用的情况下会有缺陷,下面会详细说

第二种方法,通过中断来安全的结束任务。

标志位

假设有一个产生偶数的生成器,我们希望通过设置一个标志位来结束该任务,否则这个任务会一直运行下去。

定义一个偶数生成任务,示例代码如下

public class EvenGenerateTask implements Runnable{
    private int count = 0;
    //取消任务标志位
    private volatile boolean canceled = false;
    public synchronized int nextEven(){
        count += 2;
        return count;
    }
    @Override
    public void run() {
    //通过标志位判断任务是否运行
        while (!canceled){
           count = nextEven();
           System.out.println(count);
        }
    }
    public void stop(){
        this.canceled = true;
    }
}

启动任务,并通过标志位结束该任务。

public static void main(String[] args) throws InterruptedException {
    EvenGenerateTask evenGenerateTask = new EvenGenerateTask();
    Thread thread = new Thread(evenGenerateTask);
    //启动偶数生成任务
    thread.start();
    //运行一秒钟
    Thread.sleep(1000);
    //停止偶数生成任务
    evenGenerateTask.stop();
}

上面的代码,可以安全的实现任务的取消操作,但是对于有阻塞方法调用的时候,可能情况就不同了。

假设将产生的偶数放到一个 BlockQueue
里面。来模拟一个针对偶数的生产者和消费者模式。

例如我们的代码写成这样

public void run() {
    int result = nextEven();
    while (!canceled){
        try {
            //任务有可能阻塞在此处,一直看不到canceled的值
            //也就是任务永远不会停止,除非接触阻塞。
            blockingQueue.put(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

上面的代码, put
操作是个阻塞操作,如果队列满了,那么任务将会一直处于阻塞状态,根本不会检查取消标志位,所以无论我们是否调用了取消方法,都没办法取消任务了。

中断

对于上面的场景,一般通过中断的方式来安全的取消任务。

每个线程都有一个表示线程中断状态的标志位,当线程处于中断状态时,该标志位为 true
。此外每个线程还具有如下三个与中断相关的方法。

interrupt
:该方法能够中断目标线程,并设置线程的中断状态。

isInterrupted
:该方法会返回一个线程的中断状态。

interrupted
:该方法可以清除当前线程的中断状态,并返回当前的值。

注意:调用 interrupt
方法时,只是向目标线程发送了中断的请求,并不代表线程会立刻中断正在执行的任务,也就是是否中断看心情。但是大部分都会立刻中断。

示例,通过中断取消偶数生成,核心代码如下

@Override
public void run() {
    int result = nextEven();
    //通过线程的中断状态,来决定是否退出任务
    while (!Thread.currentThread().isInterrupted()){
        try {
            blockingQueue.put(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public void cancel(){
    //发送中断请求
    interrupt();
}

在 上一篇
文章中,我们推荐使用 Executor
的方式执行任务,不建议大家要使用 Thread
的方式。如果使用 Executor
的方式启动任务,(屏蔽了 Thread
的概念)该如何中止任务呢?答案就是 Future

Future
提供了一个 cancle
方法,该方法接受一个 boolean
类型的参数,如果为 true
,且任务正在被某个线程执行,那么这个线程可以被中断。如果为 false
,表示任务如果没有启动,那么就不要启动了。

示例代码如下:

ExecutorService exec = Executors.newCachedThreadPool();
Future<?> task = exec.submit(evenGenerateTask);
try {
    task.get(5, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException e) {
    //可以重新抛出异常
}finally {
    //超时没有返回的任务将被中断
    task.cancel(true);
}

上面的内容介绍的是能够响应中断的阻塞操作,还有一种阻塞线程是不能响应中断,比如 I/O
操作的 read()
write()
方法,比如在等待获取某个内置的时候,也是无法响应中断的。对于这种情况,我们可以通过直接关闭底层资源的方式来结束任务。

原文 

https://hellofrank.github.io/2020/06/22/Java并发编程那些事儿-七-——取消及关闭/

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Java并发编程那些事儿(七)——取消及关闭

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址