并发编程7 - 任务取消

通常如下情况会取消:

1. 用户发起取消请求

2. 现实的活动

3. 分解任务其中一条发现了解决方案,其他的就可以取消了

4. 分解任务其中一条发现了对于其他任务都有影响的错误,比如磁盘空间已满,其他的可以取消了

5. 关闭,  当执行器关闭的时候,必须对正在处理及等待处理的任务进行优雅的关闭。

一个最简单的方式是,加上取消标记,cancel方法设置取消标记。 主流程中判断取消标记,进行操作

public class TestCallable {
    public static void main(String[] args) {
        ThreadTest thread = new ThreadTest();
        thread.start();

        thread.cancel();
    }
}

class ThreadTest extends Thread{
    private boolean cancel = false;
    @Override
    public void run() {
        while(!cancel){
           doSomething();
        }
        System.out.println("被取消了");
    }

    private void doSomething() {
        // ...
    }

    public void cancel(){
        this.cancel = true;
    }
}

这样有个问题,如果doSomthing()的时候有阻塞,就永远不会监测到cancel的状态,  在JDK中我们还能使用专门为了取消而存在的中断方法。

上面的代码就会改为这样:

public class TestCallable {
    public static void main(String[] args) {
        ThreadTest thread = new ThreadTest();
        thread.start();

        thread.cancel();
    }
}

class ThreadTest extends Thread {
    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                doSomething();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("被取消了");
    }

    private void doSomething() throws InterruptedException {
        // ...
        TimeUnit.SECONDS.sleep(1000);
    }

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

这里再来看一个用法,使用超时加上中断来决定任务执行多久:

public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        final ThreadTest thread = new ThreadTest();
        service.schedule(new Runnable() {
            @Override
            public void run() {
                thread.cancel();
            }
        }, 1, TimeUnit.SECONDS);
        thread.start();
    }

中断策略

给线程加中断有一个原则就是一定要清楚中断策略,否则就不要使用中断方法。

使用Future完成取消

例如:

public class TestCallable {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(1);
        ThreadTest thread = new ThreadTest();
        Future f = service.submit(thread);
        Future f2 = service.submit(new ThreadTest());
        try {
            f.get(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
            f.cancel(true);
            f2.cancel(false);
        }
    }
}

class ThreadTest extends Thread {
    @Override
    public void run() {
        System.out.println("开始运行1");

        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("执行完成1");
    }
}

其中断策略为:

.cancel(true),表示如果运行尝试对当前线程进行中断,一般是调用当前线程的.interrupt()方法

.cancel(false),表示如果没运行则不会运行这个线程了,如果运行了,会等到运行完成。

停止基于线程的服务

比如生产者消费者模式。

因为其使用了take()阻塞的方法,能够响应中断,所以如果生产者阻塞了不是问题,但是这样中断可能不太好。

我们可以加入状态标志,当设置了关闭标志之后,再生产就会抛出异常,  这个跟Executor的shutdown方法是一样的。

还有一种方式是使用致命药丸,就是在队列中加入一个特殊的任务,执行到这个药丸就停止服务。

使用TrackingExecutor类还能够获取已经取消了的任务, 用exec.getCancelledTasks()来获取

任务中的异常处理

在Executors.newFixedThreadPool(int , ThreadFactory threadFactory)中的第二个构造参数,是当一个线程异常中断的时候从这个factory中创建新的线程补充进去,可以用来做异常处理

或者一般情况下,使用Future.get方法可以得到异常

JVM关闭

正常关闭,System.exit()可以注册关闭钩子如下:

public class TestCallable {
    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                System.out.println("运行于JVM退出之前");
            }
        });
        System.exit(0);
    }
}

后台线程

daemon的线程,不会影响线程的退出。不会执行finally块,通常用于内部的事务处理。

避免使用Finalizer.不提供任何保证,并且会带来巨大的性能开销。

时间: 2025-01-02 22:09:40

并发编程7 - 任务取消的相关文章

Java并发编程相关面试问题

基础概念 1.什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)? 原子操作(atomic operation)意为"不可被中断的一个或一系列操作" .处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作. 在Java中可以通过锁和循环CAS的方式来实现原子操作. CAS操作--Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作. 原子操作是

并发编程模型

原文链接 作者: Jakob Jenkov 译者: 林威建 [weakielin@gmail.com] 并发系统可以采用多种并发编程模型来实现.并发模型指定了系统中的线程如何通过协作来完成分配给它们的作业.不同的并发模型采用不同的方式拆分作业,同时线程间的协作和交互方式也不相同.这篇并发模型教程将会较深入地介绍目前(2015年,本文撰写时间)比较流行的几种并发模型. 并发模型与分布式系统之间的相似性 本文所描述的并发模型类似于分布式系统中使用的很多体系结构.在并发系统中线程之间可以相互通信.在分

《Java 7并发编程实战手册》第四章线程执行器

感谢人民邮电大学授权并发网发布此书样章,新书购买传送门=>当当网 本章将介绍下列内容: 创建线程执行器 创建固定大小的线程执行器 在执行器中执行任务并返回结果 运行多个任务并处理第一个结果 运行多个任务并处理所有结果 在执行器中延时执行任务 在执行器中周期性执行任务 在执行器中取消任务 在执行器中控制任务的完成 在执行器中分离任务的启动与结果的处理 处理在执行器中被拒绝的任务 4.1 简介 通常,使用Java来开发一个简单的并发应用程序时,会创建一些 Runnable 对象,然后创建对应的 Th

《C#并发编程经典实例》—— 超时

声明:本文是<C#并发编程经典实例>的样章,感谢图灵授权并发编程网站发布样章,禁止以任何形式转载此文. 问题 我们希望事件能在预定的时间内到达,即使事件不到达,也要确保程序能及时进行响应. 通常此类事件是单一的异步操作(例如,等待 Web 服务请求的响应). 解决方案 Timeout 操 作 符 在 输 入 流 上 建 立 一 个 可 调 节 的 超 时 窗 口. 一 旦 新 的 事 件 到 达, 就 重 置 超 时 窗 口. 如 果 超 过 期 限 后 事 件 仍 没 到 达,Timeout

Doug Lea并发编程文章全部译文

Doug Lea's Home Page 如果IT的历史,是以人为主体串接起来的话,那么肯定少不了Doug Lea.这个鼻梁挂着眼镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容,服务于纽约州立大学Oswego分校计算机科学系的老大爷. 说他是这个世界上对Java影响力最大的个人,一点也不为过.因为两次Java历史上的大变革,他都间接或直接的扮演了举足轻重的角色.一次是由 JDK 1.1到JDK 1.2,JDK1.2很重要的一项新创举就是Collections,其Collections的概念

Erlang入门(二)—并发编程

Erlang中的process--进程是轻量级的,并且进程间无共享.查了很多资料,似乎没人说清楚轻量级进程算是什么概念,继续查找中...闲话不提,进入并发编程的世界.本文算是学习笔记,也可以说是<Concurrent Programming in ERLANG>第五张的简略翻译. 1.进程的创建     进程是一种自包含的.分隔的计算单元,并与其他进程并发运行在系统中,在进程间并没有一个继承体系,当然,应用开发者可以设计这样一个继承体系.     进程的创建使用如下语法: Pid = spaw

Erlang并发编程介绍_Erlang

Erlang中的process--进程是轻量级的,并且进程间无共享.查了很多资料,似乎没人说清楚轻量级进程算是什么概念,继续查找中...闲话不提,进入并发编程的世界.本文算是学习笔记,也可以说是<Concurrent Programming in ERLANG>第五张的简略翻译. 1.进程的创建     进程是一种自包含的.分隔的计算单元,并与其他进程并发运行在系统中,在进程间并没有一个继承体系,当然,应用开发者可以设计这样一个继承体系.     进程的创建使用如下语法: 复制代码 代码如下:

Java并发编程系列之一:并发机制的底层原理

前言 并发编程的目的是让程序运行更快,但是使用并发并不定会使得程序运行更快,只有当程序的并发数量达到一定的量级的时候才能体现并发编程的优势.所以谈并发编程在高并发量的时候才有意义.虽然目前还没有开发过高并发量的程序,但是学习并发是为了更好理解一些分布式架构.那么当程序的并发量不高,比如是单线程的程序,单线程的执行效率反而比多线程更高.这又是为什么呢?熟悉操作系统的应该知道,CPU是通过给每个线程分配时间片的方式实现多线程的.这样,当CPU从一个任务切换到另一个任务的时候,会保存上一个任务的状态,

并发编程(二):分析Boost对 互斥量和条件变量的封装及实现生产者消费者问题

请阅读上篇文章<并发编程实战: POSIX 使用互斥量和条件变量实现生产者/消费者问题>.当然不阅读亦不影响本篇文章的阅读. Boost的互斥量,条件变量做了很好的封装,因此比"原生的"POSIX mutex,condition variables好用.然后我们会通过分析boost相关源码看一下boost linux是如何对pthread_mutex_t和pthread_cond_t进行的封装. 首先看一下condition_variable_any的具体实现,代码路径:/