并发编程之同步/异步/回调/任务 工作流程分析图解

在现代软件开发中,多线程和异步编程已成为提升应用性能和响应速度的关键技术。Java提供了强大的并发工具,如ThreadRunnableCallableFuture,这些工具使开发者能够有效地管理线程和任务。ThreadRunnable允许我们创建和执行线程,而CallableFuture则提供了异步计算结果的机制。这些工具和概念不仅帮助我们解决了多线程环境下的复杂问题,还提供了处理暂时性故障和优化资源访问的策略。对于任何希望深入理解并发编程的Java开发者来说,掌握这些基础概念和工具是必不可少的。

1、同步与异步

1.1 同步与异步设计图

  1. 同步处理:指向同步处理的节点,包括阻塞当前线程、顺序执行和等待结果。
  2. 异步处理:指向异步处理的节点,包括不阻塞当前线程、并行执行和回调结果。
  • 同步处理

    • 阻塞当前线程:在等待操作完成时,当前线程不能执行其他任务。
    • 顺序执行:任务按照顺序一个接一个地执行。
    • 等待结果:线程等待操作完成并获取结果。
  • 异步处理

    • 不阻塞当前线程:在等待操作完成时,当前线程可以继续执行其他任务。
    • 并行执行:多个任务可以同时执行,提高效率。
    • 回调结果:操作完成后,通过回调函数或其他机制返回结果。

1.2 同步处理时序图

  1. 客户端请求数据:客户端发起一个请求,要求同步API提供数据。
  2. 同步API查询数据库:同步API将请求转发到数据库。
  3. 数据库返回结果:数据库处理请求并将结果返回给同步API。
  4. 同步API返回结果:同步API将数据库的结果返回给客户端。

1.3 异步处理时序图

  1. 客户端发起异步请求:客户端向异步API发起一个请求,要求查询数据,但不会等待响应。
  2. 异步API查询数据库:异步API将查询任务发送到数据库,并立即返回,不会阻塞。
  3. 数据库处理完毕:数据库在后台处理查询任务。
  4. 调用回调函数:一旦数据库处理完毕,异步API调用预先定义的回调函数。
  5. 返回结果给客户端:回调函数将结果返回给客户端。

1.3 回调时序图

  1. 客户端注册回调并发起异步请求:客户端向异步API发起请求,并提供一个回调函数,用于在异步操作完成后接收结果。
  2. 异步API立即返回:异步API接收请求后立即返回,不等待异步操作完成。
  3. 客户端继续执行:客户端可以继续执行其他任务,而不必等待异步操作的结果。
  4. 异步API执行异步操作:异步API在后台执行异步操作。
  5. 异步操作完成:一旦异步操作完成,异步API调用之前注册的回调函数。
  6. 回调执行:回调函数被执行,处理异步操作的结果。
  7. 返回结果给客户端:回调函数将异步操作的结果返回给客户端。

2、任务执行单元

2.1 Thread 工作模式

  1. 创建 Thread 对象 :通过 new Thread() 或其子类构造函数创建一个新的线程对象。
  2. 设置 Runnable 任务 :为线程对象设置一个实现了 Runnable 接口的任务。
  3. 启动线程 :调用线程对象的 start() 方法来启动线程。
  4. 线程状态 :线程可以处于以下几种状态之一:
    • 运行中 :线程正在执行其 run() 方法。
    • 阻塞:线程等待监视器锁。
    • 等待:线程等待某个条件或超时。
    • 终止:线程执行完毕或被中断。
  5. 执行 run 方法 :线程开始执行其 run() 方法。
  6. 调用 Runnable.runThread 类的 run() 方法内部调用 Runnable 任务的 run() 方法。
  7. 执行任务 :实际的 Runnable 任务开始执行。
  8. 任务执行完毕Runnable 任务执行完毕。
  9. 线程结束:任务执行完毕后,线程结束其生命周期。
  10. 等待解锁:如果线程需要访问同步代码块或方法,它可能会因为等待锁而被阻塞。
  11. 等待条件 :线程可能在 wait()join() 或其他等待条件下等待。

3、任务单元

3.1 Runnable 无返回值

Runnable 接口设计

Runnable 接口是 Java 中的一个功能性接口,用于那些可以被线程执行的任务。它只包含一个方法 run(),该方法不接受参数,也不返回任何值(即返回类型为 void)。

接口定义
java 复制代码
@FunctionalInterface
public interface Runnable {
    void run();
}
使用 Runnable 的步骤
  1. 实现 Runnable 接口 : 创建一个类实现 Runnable 接口,并覆盖 run() 方法来定义任务的执行逻辑。
java 复制代码
public class MyRunnableTask implements Runnable {
    @Override
    public void run() {
        // 任务执行的代码
        System.out.println("任务正在执行");
    }
}
  1. 创建 Thread 对象 : 创建 Thread 对象时,将实现了 Runnable 接口的类的实例作为参数传递给 Thread 构造函数。
java 复制代码
public class Main {
    public static void main(String[] args) {
        MyRunnableTask task = new MyRunnableTask();
        Thread thread = new Thread(task);
        thread.start(); // 启动线程,执行任务
    }
}
  1. 启动线程 : 调用 Thread 对象的 start() 方法来启动线程。这将导致在新线程中异步执行 run() 方法。

3.2 Callable 有返回值

Callable 接口设计

Callable接口是Java并发编程中的一部分,它允许任务返回结果和抛出异常。以下是Callable接口的基本设计和用法的说明:

接口定义
java 复制代码
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
  • Callable<V>Callable是一个泛型接口,V代表返回值的类型。
  • call():这是一个方法,它不接受任何参数,可以返回任何类型的值(V),并且可以抛出任何类型的异常。
使用 Callable 的步骤
  1. 实现 Callable 接口 : 创建一个类实现 Callable 接口,并覆盖 call() 方法来定义任务的执行逻辑和返回结果。
java 复制代码
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 任务执行的代码
        return "任务结果";
    }
}
  1. 创建 FutureTask 对象 : 将实现了 Callable 接口的类的实例传递给 FutureTask 构造函数。
java 复制代码
public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable task = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(task);
        Thread thread = new Thread(futureTask);
        thread.start(); // 启动线程,执行任务

        // 获取任务结果
        String result = futureTask.get();
        System.out.println("任务结果: " + result);
    }
}
  1. 启动线程 : 调用 Thread 对象的 start() 方法来启动线程。这将导致在新线程中异步执行 FutureTaskrun() 方法,该方法内部会调用 Callablecall() 方法。

4、异步结果

4.1 Future

  1. 提交异步任务 :通过 ExecutorService 提交一个异步任务。
  2. ExecutorService:执行异步任务的服务。
  3. Future对象ExecutorService 提交任务后返回一个 Future 对象,该对象可以用来检查计算是否完成、等待计算完成、取消任务和获取计算结果。
  4. 检查完成 :通过 Future 对象的 isDone() 方法检查异步任务是否已完成。
  5. 已完成 :如果任务已完成,通过 Future 对象的 get() 方法获取结果。
  6. 获取结果:处理异步任务的结果。
  7. 未完成:如果任务未完成,可以选择等待或取消任务。
  8. 等待或取消:等待任务完成或取消任务。
  9. 等待超时:设置超时时间等待任务完成。
  10. 取消任务:尝试取消任务。
  11. 处理结果:处理异步任务的结果。
  12. 处理超时:处理等待超时的情况。
  13. 处理取消 :处理任务取消的情况
  14. 客户端提交任务 :客户端向执行器服务提交一个实现了 Callable 接口的异步任务。
  15. 执行器服务执行任务:执行器服务接收到任务后,开始执行异步任务。
  16. 异步任务设置结果 :异步任务执行完毕后,将结果设置到与 Future 对象关联的内部存储中。
  17. 客户端获取结果 :客户端调用 Future 对象的 get() 方法来获取异步任务的结果。
  18. 任务已完成 :如果异步任务已经完成,Future 对象将返回结果给客户端。
  19. 任务未完成 :如果异步任务还未完成,Future 对象将导致客户端阻塞等待,直到任务完成并返回结果。
  • Future :这是一个接口,定义了以下方法:
    • cancel(boolean mayInterruptIfRunning):尝试取消执行任务。
    • isCancelled():检查任务是否已被取消。
    • isDone():检查任务是否已完成。
    • get():获取任务的结果,如果任务尚未完成,则阻塞等待。
    • get(long timeout, TimeUnit unit):在指定的超时时间内获取任务的结果。

4.2 FutureTask

  1. 创建 FutureTask 对象 :创建一个 FutureTask 对象,它封装了一个 Callable 对象(如果任务有返回值)或 Runnable 对象(如果任务没有返回值)。
  2. 提交任务 :决定是通过 ExecutorService 提交任务还是直接在当前线程执行。
  3. 执行任务 :如果通过 ExecutorService 提交,任务将被安排在线程池中的某个线程执行。如果是直接执行,则调用 FutureTaskrun 方法。
  4. 任务状态:检查任务的执行状态。
  5. 完成:如果任务正常完成,则设置任务的结果。
  6. 被取消:如果任务在执行前被取消,则标记任务为取消状态。
  7. 异常:如果任务执行过程中抛出异常,则设置异常信息。
  8. 提供结果给调用者 :一旦任务完成(无论是正常完成、被取消还是抛出异常),FutureTask 将能够提供结果给调用 get 方法的线程。
  9. 客户端提交任务 :客户端向执行器服务提交一个实现了 Callable 接口的任务。
  10. 执行器服务创建 FutureTask :执行器服务接收到任务后,创建一个 FutureTask 对象来封装这个任务。
  11. 执行任务 :执行器服务执行 FutureTaskrun() 方法,这将导致在新线程中异步执行 Callable 任务的 call() 方法。
  12. 执行任务逻辑Callable 任务执行其逻辑,并可能设置结果或异常。
  13. 通知完成 :如果客户端已经调用了 FutureTaskget() 方法并正在等待,FutureTask 将通知客户端任务已完成。
  14. 客户端获取结果 :客户端调用 FutureTaskget() 方法来获取异步任务的结果。
  15. 任务已完成 :如果任务已完成,FutureTask 返回结果给客户端。
  16. 任务未完成 :如果任务未完成,FutureTask 将导致客户端阻塞等待。
  • FutureTask :这是一个类,它实现了 RunnableFuture 接口。它包含任务的执行结果(result)、任务完成状态(done)和取消状态(cancelled)。
  • Callable:这是一个接口,它允许任务返回结果和抛出异常。
  • Runnable:这是一个接口,它允许任务被线程执行,但不返回结果和异常。
  • TimeUnit :这是一个枚举,它定义了时间单位,用于 get 方法的超时参数。
相关推荐
葫芦和十三6 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp6 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑7 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯8 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan10 小时前
多Agent之间的区别
后端
青石路11 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充12 小时前
1.面向对象设计思想
后端
IT_陈寒12 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro13 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗13 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端