JUC编程03

JUC编程

10 线程池(重点)

1、Executors

Executors是Java并发包中,用来"快速创建和管理线程池"的工具类。它本身并不执行任务,只是一个"线程池工厂"。

Executors是JDK提供的线程池工厂类,用于快速创建常见的ExecutorService实现,它通过封装ThreadPoolExecutor简化并发编程,但其默认实现存在无界队列或无限线程的问题,在生产环境中通常建议直接使用ThreadPoolExecutor自定义线程池参数。

三大方法

一、Executors.newSingleThreadExecutor()

  1. 作用

    所有任务严格按照提交顺序,一个一个地执行

    • 同一时间只会有一个线程
    • 不会并发执行
    • 比 new Thread().start() 更安全(县城一场会自动补新线程)
  2. 底层原理(重要)

    源码等价于

    java 复制代码
    new ThreadPoolExecutor(
        1,                      // corePoolSize
        1,                      // maximumPoolSize
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>() // 无界队列
    );
    • 核心线程数=最大线程数=1
    • 使用无界队列LinkedBlockingQueue
    • 永远只有一个工作线程
    • 任务堆积在队列里,排队执行
    • 线程异常?线程池会自动创建一个新的线程继续执行后续任务
  3. 典型使用场景:需要顺序性保证的任务

    • 日志写入(避免乱序)
    • 文件操作
    • 数据库单线程更新
    • 事件穿行处理(消息消费)
  4. 风险点:**任务过多会OOM。因为LinkedBlockingQueue是无界队列,**允许的请求队列长度为 Integer.MAX_VALUE(约21亿),会堆积大量请求。

二、Executor.newFixedThreadPool(n)

  1. 作用

    固定数量的线程并发执行任务

    • 同时最多n个任务执行
    • 超过的任务进入队列等待
  2. 底层原理

    源码等价于:

    java 复制代码
    new ThreadPoolExecutor(
     	n,                                 // corePoolSize
        n,                                 // maximumPoolSize
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>()// 无界队列
    );
    • 线程数永远是n
    • 任务多了就进队列
    • 不会创建超过n个线程
  3. 典型使用场景

    CPU密集型任务

    • 图片处理
    • 视频转码
    • 数学计算
    • 一般经验:线程数 ≈ CPU核心数
  4. 风险点:仍然可能OOM。因为LinkedBlockingQueue是无界队列,允许的请求队列长度为 Integer.MAX_VALUE(约21亿),会堆积大量请求。

三、Executor.newCachedThreadPool()

  1. 作用

    来任务就创建线程,用完就回收

    • 线程数几乎无限
    • 适合大量短生命周期任务
  2. 底层原理

    源码等价于:

    java 复制代码
    new ThreadPoolExecutor(
        0,                          // corePoolSize
        Integer.MAX_VALUE,          // maximumPoolSize
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>()
    );
  3. 典型使用场景

    I/O密集型、短任务

    • 网络请求
    • RPC调用
    • 异步日志
    • 突发流量
  4. 风险点:极易线程爆炸 ,允许的创建线程数量为 Integer.MAX_VALUE(约21亿),可能会创建大量的线程。

    大量请求 → 大量线程 → CPU飙升 → OOM/系统崩溃

三种方法对比
方法 线程数量 队列 是否可能OOM 适合场景
Single 1 LinkedBlockingQueue 顺序执行
Fixed n LinkedBlockingQueue CPU密集
Cache 不限 SynchronousQueue I/O密集
为什么不推荐直接用Executors?

阿里Java开发手册明确禁止。原因:所有Executors默认实现,要么无限队列。要么无限线程。

正确姿势

java 复制代码
new ThreadPoolExecutor(
    corePoolSize,
    maxPoolSize,
    keepAliveTime,
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(capacity),
    new ThreadPoolExecutor.AbortPolicy()
);
  • 可控
  • 可预期
  • 不会炸内存
总结

newSingleThreadExecutor 和 newFixedThreadPool 本质都是使用无界队列 ,存在 OOM 风险;newCachedThreadPool 使用 SynchronousQueue,线程数不受限,存在线程爆炸风险,实际生产中应通过 ThreadPoolExecutor 自定义参数来创建线程池。

2、ThreadPoolExecutor

ThreadPoolExecutor是Java中线程池的核心实现类,通过"核心线程 + 最大线程 + 队列 + 拒绝从策略"的组合,实现对并发任务执行的"限流、复用、调度和保护"。

一、ThreadPoolExecutor的核心作用
  1. 统一管理线程生命周期,避免频繁 new Thread()

    • 创建
    • 复用
    • 回收
    • 异常恢复
  2. 控制并发规模(核心),不是来多少任务就执行多少

    通过corePoolSizemaximumPoolSizeworkQueue对系统施加上限

  3. 提供"背压 + 保护机制"

    • 队列满了怎么办?
    • 线程到上限了怎么半?

    交给RejectExecutionHandler

二、构造方法七大参数解释(重点)
java 复制代码
public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler
)
  1. corePoolSize 核心线程数

    线程池希望长期保持的线程数量:若当前线程数 < corePoolSize,则直接创建新线程执行任务

    • 即使空闲,也不会被回收
    • 优先创建

    execute(task)

    1. 如果当前工作线程 < corePoolSize
      • 直接新建线程(核心线程)来执行这个任务
      • 不会先进队列
    2. 如果当前工作线程数 ≥ corePoolSize
      • 不在因为来新任务就加线程,转而先尝试入队(由workQueue决定)
  2. maximumPoolSize 最大线程数

    线程池能创建的线程总数上限 (包含核心线程 + 非核心线程),只有在"队列满了塞不进去"的情况下,线程池才会尝试把线程数从 corePoolSize 继续扩到 maximumPoolSize

    • 空闲时,超过corePoolSize的那部分非核心线程会在超过keepAliveTime后被回收

    当前线程数 ≥ corePoolSizeworkQueue.offer(task)失败(通常意味着队列满了)时:

    1. 如果当前线程数 < maximumPoolSize
      • 新建非核心线程来执行任务(扩容)
    2. 否则
      • 触发拒绝策略 handler
  3. keepAliveTime + unit 线程存活时间

    含义:非核心线程的空闲存活时间

    • 超时后会被回收
    • 减少资源占用

    如果开启allowCoreThreadTimeOut(true),则核心线程也可回收

  4. workQueue 任务队列(核心之一)

    含义:存放"等待执行任务"的缓冲区。常见实现:

    队列 特点
    ArrayBlockingQueue 有界
    LinkedBlockingQueue 可无界
    SynchronousQueue 不存任务
    PriorityBlockingQueue 带优先级

    队列类型决定线程池行为

  5. threadFactory 线程工厂

    含义:定义线程"如何被创建"

    用途:

    • 线程命名
    • 设置是否守护线程
    • 设置优先级
    • 统一异常处理器
  6. handler 拒绝策略

    含义:当前任务无法被执行时的处理方式

    线程池的处理能力是有限的(线程数有上限、队列容量有限)。当任务提交速度 长期 > 线程池处理速度,就会出现"挤爆":

    • 线程已经到 maximumPoolSize
    • 队列也满了(或队列根本不存任务,比如 SynchronousQueue

    内置策略

    策略 行为 特性
    AbortPolicy 抛出异常(默认) 不能丢,必须明确失败并报警
    CallerRunsPolicy 调用者执行 尽量不丢,但可以变慢、需要背压
    DiscardPolicy 直接丢弃 可丢,丢了也没大影响
    DiscardOldestPolicy 丢弃最老任务 只关心最新,旧的没价值

    四种拒绝策略

    1. AbortPolicy

      行为:直接抛 RejectedExecutionException

      ✅ 优点:快速暴露问题,防止"默默丢任务"

      ❌ 缺点:如果上层没捕获,可能把业务线程打崩/请求失败

      适用:任务不能丢、宁可失败也要报警(金融扣款、订单创建、关键写入)。

    2. CallerRunsPolicy

      行为:让"提交任务的线程"自己去执行该任务(而不是线程池)。

      ✅ 优点:天然背压:提交者被迫干活 → 提交速度下降 → 系统缓一口气

      ✅ 优点:不丢任务(除非线程池关闭)

      ❌ 缺点:可能把调用方拖慢,甚至拖垮关键线程(比如 Netty IO 线程、Servlet 容器线程、UI 线程)

      ❌ 缺点:会导致响应时间飙升、线程栈变复杂

      适用:允许变慢但尽量不丢任务,且"提交者线程"不是关键线程(或你能接受它变慢)。例如:异步日志、非关键后台任务。

    3. DiscardPolicy

      行为:直接丢弃任务,什么都不做、也不抛异常。

      ✅ 优点:最省事,系统最稳(不额外制造压力)

      ❌ 缺点:悄无声息丢任务,排查极难,业务可能"少执行了你还不知道"

      适用:任务可丢且有替代(比如:刷新缓存的重复任务、统计上报、埋点、低价值异步通知),并且你有监控能看到丢弃率。

    4. DiscardOldestPolicy

      行为:丢弃队列里"最老的那个任务",再尝试把当前任务入队。

      ✅ 优点:让"新任务"更容易被处理,适合"只关心最新"

      ❌ 缺点:会导致队列前面的任务饿死/丢失,而且丢的是哪个你不一定意识到

      ❌ 缺点:在某些负载下可能"循环丢弃",造成抖动

      适用:更关心最新数据(例如:状态刷新、UI 渲染更新、行情/传感器数据的刷新任务),旧的执行了也没意义。

总结

线程池承载能力(吞吐极限) = maximumPoolSize + workQueue.capacity

ThreadPoolExecutor 是 Java 线程池的核心实现类,通过 corePoolSize、maximumPoolSize 和 workQueue 控制并发规模,通过 keepAliveTime 回收空闲线程,并在资源耗尽时通过 RejectedExecutionHandler 实现系统保护,从而实现高效、可控、可扩展的并发任务执行。

3、最大线程数应该如何定义?

一、CPU密集型(CPU-bound)

任务的大部分时间都在做计算,占着 CPU 不放。

  • 线程几乎一直在计算
  • 很少阻塞或等待
  • 如:数学计算、图像/视频编码、加密解密、机器学习推理、排序/搜索/大量循环运算......

常见经验公式:最大线程数 ≈ CPU 核数 或 CPU 核数 + 1

二、IO密集型

任务的大部分时间在"等外部资源",CPU反而是空着的。

  • 线程经常阻塞
  • CPU利用率不高,但线程没法干别的
  • 如:
  • 读写磁盘、网络请求(HTTP / RPC)、数据库访问、文件上传下载、远程服务调用

常用经验公式:最大线程数 ≈ CPU 核数 / (1 - 阻塞系数)

  • 阻塞系数 ≈ IO等待时间占比
    • 0.8(80%等待) → 8/0.2 = 40
    • 0.9(90%等待) → 8/0.1 = 80
三、设置最大线程数时,"密集型"的真正含义

你在设置 maximumPoolSize 时,实际上是在回答这两个问题:

  1. 线程是在用CPU,还是在等待资源?
  2. CPU空闲时间能不能被别的线程利用?
类型 线程状态 maxPoolSize设置
CPU密集型 一直RUNNING 上限 ≈ CPU核数
IO密集型 大量WAITTING 上限 >> CPU核数
总结

CPU 密集型任务主要消耗计算资源,线程几乎不阻塞,因此线程数不宜超过 CPU 核心数,否则会因频繁上下文切换降低性能;

IO 密集型任务大部分时间在等待外部资源,CPU 经常空闲,通过增加线程数可以提高 CPU 利用率,因此最大线程数通常远大于 CPU 核心数。

11 四大函数式接口(重点,必须掌握)

新时代程序员技能:lambda表达式、链式编程、函数式接口、Stream流式计算

接口 作用 直觉理解
Function<T, R> 把T变成R 加工、映射、转换
Predicate<T> 判断T是不是 条件、过滤、if
Consumer<T> 消费T,不返回 干活、使用、输出
Supplier<T> 提供T,没有输入 生产、获取、懒加载

口诀:Function 做转换,Predicate 做判断,Consumer 只干活,Supplier 只给货

一、Function<T, R>------把一个东西变成另一个

  1. 定义

    java 复制代码
    @FunctionalInterface
    public interface Function<T, R> {
        R apply(T t);
    }
    • 有输入
    • 有输出
    • 重点是"转换关系"

二、Predicate<T>------判断一个条件,真 or 假

  1. 定义

    java 复制代码
    @FunctionalInterface
    public interface Predicate<T> {
        boolean test(T t);
    }
    • 有输入
    • 输出永远是boolean
    • 用于判断/过滤/条件

三、Consumer<T>------只使用,不返回

  1. 定义

    java 复制代码
    @FunctionalInterface
    public interface Consumer<T> {
        void accept(T t);
    }
    • 有输入
    • 没有返回值
    • 重点是副作用

四、Supplier<T>------只负责提供,不接受输入

  1. 定义

    java 复制代码
    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }
    • 没有输入
    • 有输出
    • 常用于延迟创建/懒加载

12 Stream流式计算

一、什么是Stream

Stream 是一种对数据进行流式计算的抽象,不存储数据,只描述计算过程

Stream的三个关键特征

特征 说明
不存数据 数据仍在Collection/数组中
惰性执行 不调用终止操作就不执行
一次性 Stream只能使用一次

Stream ≠ Colletion(非常重要)

对比 Collection Stream
是否存储数据
是否可复用
关注点 数据 计算
编程风格 命令式(给出步骤) 声明式(只要结果)

Stream更像"流水线设计图",不是仓库

二、什么是流式计算

流式计算的思想

数据像水一样,一个元素一个元素地流过一系列操作

markdown 复制代码
元素1 → filter → map → forEach
元素2 → filter → map → forEach

而不是:

markdown 复制代码
全部 filter → 全部 map → 全部 forEach

这种方式叫:

  • 流水线执行
  • 垂直执行
  • Pipeline Fusion

三、Stream的整体结构

Stream一定包含三部分

markdown 复制代码
数据源 → 中间操作(0~N) → 终止操作(1)

实例

java 复制代码
list.stream()               // 数据源
    .filter(x -> x > 3)     // 中间操作
    .map(x -> x * 2)        // 中间操作
    .forEach(System.out::println); // 终止操作
  • 没有终止操作,整个Stream什么都不会执行

四、Stream常用方法

1、创建Stream(数据源)
  1. Collection.stream()

    java 复制代码
    list.stream();
    list.parallelStream();

    原理:

    • 不复制数据
    • 底层基于底层基于 Spliterator
    • 并行流通过ForkJoinPool执行
  2. Stream.of()

    java 复制代码
    Stream.of(1, 2, 3);

    原理:

    • 基于数组创建流
  3. Stream.generate/iterate(无限流)

    java 复制代码
    Stream.generate(Math::random)
        .limit(3)
        .forEach(System.out::println);
    java 复制代码
    Stream.iterate(0, x -> x + 1)
        .limit(5)
        .forEach(System.out::println);
    • 无限流必须配合limit使用
2、中间操作(返回Stream,惰性)

中间操作的本质:

给 Stream 加一层"处理规则"

  1. filter------过滤

    java 复制代码
    stream.filter(x -> x > 10);
    • 使用Predicate<T>
    • 不满足条件的元素直接丢弃
    • 无状态操作
  2. map------转换

    java 复制代码
    stream.map(x -> x * 2);
    • 使用Function<T, R>
    • 一对一映射
    • 不修改原数据
  3. flatMap------扁平化

    java 复制代码
    List<List<Integer>> lists = ...
    lists.stream()
         .flatMap(List::stream)
         .forEach(System.out::println);
    • 一对多映射
    • 常用于"嵌套集合"
  4. distinct------去重

    java 复制代码
    stream.distinct();
    • 内部使用HashSet
    • 依赖equals/hashCode
    • 有状态操作
  5. sorted------排序

    java 复制代码
    stream.sorted();
    Stream.sorted(Comparator.comparingInt(User::getAge));
    • 需要缓存所有元素
    • 有状态操作
    • 会破坏元素流动
  6. limit/skip------截断

    java 复制代码
    stream.limit(5);
    stream.skip(3);
    • limit是短路操作
    • 达到数量后立即停止流水线
3、终止操作(触发执行)
  1. forEach------消费

    java 复制代码
    stream.forEach(System.out::println);
    • 使用Consumer<T>
    • 并行流中不保证顺序
    • 不建议在forEach中做复杂副作用
  2. collect------收集(常用)

    java 复制代码
    List<Integer> list = stream.collect(Collectors.toList());

    本质:

    java 复制代码
    supplier    // 创建容器
    accumulator // 添加元素
    combiner    // 并行合并
  3. reduce------规约

    java 复制代码
    int sum = stream.reduce(0, Integer::sum);
    • 把多个元素合成一个
    • 要求:
      • 无副作用
      • 满足结合律
  4. count/findFirst/anyMatch

    java 复制代码
    stream.count();
    stream.findFirst();
    stream.anyMatch(x -> x > 10)
    • findFirst/anyMatch支持短路
    • 找到结果立即停止

13 ForkJoin

一、为什么要有ForkJoin体系

  1. 传统线程池的问题:不擅长递归任务

    在普通线程池(ThreadPoolExecutor)中:

    • 任务是整体提交

    • 一个任务通常不能再拆分

    • CPU密集型任务容易:

      • 线程忙
      • CPU利用率不均
      • 某些线程闲着,某些线程累死
  2. ForkJoin要解决什么问题

    让一个大任务可以不断拆成小任务,并行执行,再把结果合并

    典型场景:

    • 大数组求和
    • 排序
    • 树/图遍历
    • Stream并行计算

二、什么是ForkJoin体系

ForkJoin是Java提供的一套用于"分治并行计算"的框架

核心思想来自算法中的:Divide&Conquer(分而治之)

ForkJoin的核心组件

组件 作用
ForkJoinPool 线程池
ForkJoinWorkerThread 工作线程
ForkJoinTask 可拆分任务
RecursiveTask 有返回值任务
RecursiveAction 无返回值任务

三、ForkJoin的基本工作流程

perl 复制代码
一个大任务
   ↓ fork
拆成多个小任务
   ↓ fork
子任务并行执行
   ↓ join
合并子任务结果

四、ForkJoin的核心思想:分治模型

典型伪代码:

java 复制代码
if(任务足够小) {
    直接计算;
}else {
    拆分成子任务;
    fork子任务;
    join子任务结果;
}
  • 递归 + 并行

五、ForkJoin的核心类

1、ForkJoinPool------专门为"分治"设计的线程池

特点

  • 默认线程数 ≈ CPU核心数
  • 专门为CPU密集型任务设计
  • 内部不使用普通阻塞队列
java 复制代码
ForkJoinPool pool = new ForkJoinPool();

与ThreadPoolExecutor的关键区别

对比 ThreadPoolExecutor ForkJoinPool
任务模型 独立任务 可拆分任务
队列 BlockingQueue 双端队列
目标 IO / 通用 CPU密集
核心机制 抢占 工作窃取
2、ForkJoinTask------抽象任务

两个常用子类

  • RecursiveTask<T>(有返回值)。

    java 复制代码
    class SumTask extends RecuisiveTask<Integer> {}
  • RecursiveAction<T>(无返回值)

    java 复制代码
    class PrintTask extends RecersiveAction {}

六、工作窃取

忙的线程不帮闲的,闲的线程主动偷活干

如何做到工作窃取

  • 每个ForkJoinWorkerThread,都有一个自己的双端队列(Deque)

  • 规则:

    • 自己提交的任务:LIFO(栈)
    • 窃取别人的任务:FIFO(队列)
    tex 复制代码
    线程 A: [ T1, T2, T3 ]
    线程 B: [ ]
    线程 B 偷走 T1

    这样做的好处:

    • 减少锁竞争
    • 提高CPU利用率
    • 局部性更好(缓存友好)

为什么不用BlockingQueue

  • 锁竞争太重
  • 不适合大量小任务
  • 工作窃取需要双端结构

七、ForkJoin示例

fork() 是"把活甩给别人"

compute() 是"自己动手干活"

join() 是"等别人把活干完,拿结果"

java 复制代码
public class ForkJoinTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Long startTime = System.currentTimeMillis();
        long sum = test01(); // Time = 413ms
//        Long sum = test02(); // Time = 229ms
//        Long sum = test03(); // Time = 84ms
        Long endTime = System.currentTimeMillis();
        System.out.println("sum = " + sum + " Time = " + (endTime - startTime) + "ms");
    }

    public static long test01() { // 直接计算
        long sum = 0L;
        for (long i = 1L; i <= 10_0000_0000; i++) {
            sum += i;
        }
        return sum;
    }

    public static Long test02() throws ExecutionException, InterruptedException { // ForkJoin
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> forkJoinTask = new SumTask(1L, 10_0000_0000L, 10000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinTask);
        return submit.get();
    }
 
    public static Long test03() { // Stream 并行计算
        return LongStream.rangeClosed(1L, 10_0000_0000L).parallel().reduce(0, Long::sum);
    }
}

class SumTask extends RecursiveTask<Long> {
    final long start;
    final long end;
    final long limit;
    SumTask(long start, long end, long limit) {
        this.start = start;
        this.end = end;
        this.limit = limit;
    }
    @Override
    protected Long compute() {
        if (end - start <= limit) {
            long sum = 0;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }else {
            SumTask sumTaskLeft = new SumTask(start, limit, start + (limit - start) / 2);
            SumTask sumTaskRight = new SumTask(limit + 1, end, limit + (end - limit - 1) / 2);
            sumTaskLeft.fork();
            Long rightResult = sumTaskRight.compute();
            Long leftResult = sumTaskLeft.join();
            return rightResult + leftResult;
        }
    }
}

八、fork() / join()

fork()

  • 把任务压入 当前线程的工作队列
  • 异步执行

join()

  • 等待子任务完成
  • 如果子任务没执行
    • 当前线程可能自己执行
    • 或等待别人执行

总结

Fork / Join 是 Java 提供的一种基于分治思想的并行计算框架,通过将大任务递归拆分为多个子任务并行执行,并结合工作窃取算法提高线程利用率,特别适合 CPU 密集型场景,也是并行 Stream 的底层执行机制。

14 异步回调

一、为什么需要Future

同步调用的问题

java 复制代码
int result = slowMethod(); // 阻塞

问题在于:

  • 当前线程必须等待结果
  • CPU / 线程资源被浪费
  • 不适合耗时操作(IO / 网络 / RPC)

Future的目标:把"任务执行"和"结果获取"分离,让任务可以异步执行。

二、Future接口详解

1、Future是什么

java 复制代码
public interface Future<V> {
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit);
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isDone();
    boolean isCancelled();
}

Future表示"一个未来才会产生的结果"

2、Future的典型使用方式

java 复制代码
ExecutorService pool = Executors.newFixedThreadPool(1);

Future<Integer> future = pool.submit(()->{
    TimeUnit.SECONDS.sleep(1);
    return 100;
});

System.out.println("主线程继续执行");

Integer result = future.get(); // 阻塞
System.out.println(result);

执行过程:

  1. 任务提交后立即返回Future
  2. 任务在其他线程执行
  3. 调用get()时阻塞等待结果

3、Future的核心作用总结

能力 说明
异步执行 任务在其他线程执行
结果占位 Future是结果的"句柄"
阻塞获取 通过 get() 拿结果
取消任务 cancel()

Future = 异步执行 + 阻塞式结果获取

三、Future的不足(重点)

Future虽然解决了"异步执行",但存在明显短板。

  1. 只能阻塞式获取结果

    java 复制代码
    future.get(); '' 必须等
    • 要么阻塞
    • 要么不停isDone()轮询
  2. 不支持异步回调

    做不到:

    java 复制代码
    任务完成后 -> 自动执行某段逻辑

    只能:

    java 复制代码
    get() -> 再手动处理
  3. 不能组合多个异步任务

    场景:

    • 任务A完成 → 再执行任务B
    • 任务A、B都完成 → 合并结果

    Future 写起来会:

    • 回调地狱
    • 阻塞嵌套
    • 代码可读性极差
  4. 异常处理不友好

    java 复制代码
    try{
        future.get();
    } catch(ExecutionException e) {
        // 包了一层异常
    }

Future 只能"拿结果",却不能"编排异步流程"。

四、FutureTask:过渡方案(了解)

java 复制代码
public class Future<V> implements RunnableFuture<V>

特点:

  • 同时是Runnable + Future
  • Callable只会执行一次(CAS保证)
  • 结果会被缓存,多线程共享

用法:

java 复制代码
FutureTask<Integer> task = new FutureTask<>(()->100);
new Thread(task).start();
 
Integer result = task.get;

但是仍然不支持回调和链式编排

五、CompletableFuture(重点)

1、CompletableFuture是什么

CompletableFuture 是对 Future 的增强版,支持异步回调、任务组合和函数式编排。

CompletableFuture = Future + 回调 + 编排 + 函数式

2、CompletableFuture解决了什么

Future的问题 CompletableFuture的解决方案
get 阻塞 thenXXX 异步回调
无法组合 thenCompose / thenCombine
异常难处理 exceptionally / handle
手动管理流程 链式声明式编排

六、 CompletableFuture 的基本使用方法

创建异步任务

  • 无返回值

    java 复制代码
    CompletableFuture.runAsync(()->{
        System.out.println("异步任务")
    })
  • 有返回值

    java 复制代码
    CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(()->100);

默认线程池:ForkJoinPool.commonPool()

也可以指定线程池:

java 复制代码
CompletableFuture.supplyAsync(()->100, executor);

七、什么是"异步回调"(核心)

不是我去等结果,而是结果出来后自动通知我

三类回调方法(重要)

  1. thenApply------结果转换(Function)

    java 复制代码
    CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(()->10)
        .thenApply(x->x * 2);
    • 有输入
    • 有输出
    • 类似Stream的map
  2. thenAccept------消费结果(Consumer)

    java 复制代码
    CompletableFuture.supplyAsync(()->10)
        .thenAccept(System.out::println)
        .thenAccept(x->System.out.println(x));
    • 有输入
    • 无返回值
    • 常用于打印、保存、发送
  3. thenRun------不关心结果

    java 复制代码
    CompletableFuture.supplyAsync(()->100)
        .thenRun(()->System.out.println("完成"));

八、异步任务的组合能力(CompletableFuture最大优势)

  1. thenCompose------串行依赖(扁平化)

    java 复制代码
    CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(()->10)
        .thenCompose(x->
                    CompletableFuture.supplyAsync(()->x * 2)
                    );
    • A完成后,在启动B
  2. thenCombine------并行合并

    java 复制代码
    CompletableFuture<Integer> cf1 = CompletableFuture.supply(()->10);
    CompletableFuture<Integer> cf2 = CompletableFuture.supply(()->20);
    
    CompletableFuture<Integer> result = cf1.thenCombine(f2, Integer::sum);
    • 两个任务并行执行
    • 都完成后合并结果

九、异常处理(CompletableFuture的强项)

  1. exceptionally------兜底异常

    java 复制代码
    CompletableFuture.supply(()->1 / 0)
        .exceptionally(ex->-1);
  2. handle------成功 / 失败统一处理

    java 复制代码
    CompletableFuture.supplyAsync(()->10)
        .handle((rex, ex)->{
            if(ex != nell) return -1;
            return res*2;
        });

十、CompletableFuture 项目案例

java 复制代码
public class CompletableFutureTest {
    // 真实项目中使用自定义线程池 避免吧commonPool用满
    private static final ExecutorService IO_POOL = Executors.newFixedThreadPool(
            6,
            (r)->{
                Thread t = new Thread(r);
                t.setName("io_pool-" + t.getId());
                t.setDaemon(false);
                return t;
            });
    private static final Random RND = new Random();
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFutureTest demo = new CompletableFutureTest();
        long start = System.currentTimeMillis();

        // 1. 并发查询三个"互不依赖"的信息
        CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(demo::queryUser, IO_POOL);
        //System.out.println(userFuture.get());
        CompletableFuture<Integer> scoreFuture = CompletableFuture.supplyAsync(demo::queryScore, IO_POOL);
        //System.out.println(scoreFuture.get());
        CompletableFuture<Integer> orderCountFuture = CompletableFuture.supplyAsync(demo::queryOrderCount, IO_POOL);

        // 2. thenApply 对 积分结果 做转换 -> 生成等级(VIP / NORMAL)
        CompletableFuture<String> levelFuture = scoreFuture.thenApply((score)-> score > 80 ? "VIP" : "NORMAL");

        // 3. thenCombine 并行合并 user + level -> 生成 UserDTO
        CompletableFuture<UserDTO> userDTOFuture =
                userFuture.thenCombine(levelFuture, (user, level)->new UserDTO(user.id, user.name, level));

        // 4. 合并订单数 UserDTO + orderCount
        CompletableFuture<UserDTO> fullDTOfuture =
                userDTOFuture.thenCombine(orderCountFuture, (dto, orderCount)->{
                   dto.orderCount = orderCount;
                   return dto;
                });

        // 5. 串行依赖(必须先拿到优惠券 userID 才能查优惠券)
        CompletableFuture<List<Coupon>> couponFuture =
                userFuture.thenCompose((user)->
                        CompletableFuture.supplyAsync(()->demo.queryCoupons(user.id), IO_POOL));

        // 6. 再把优惠券结果合并进 DTO(dto + coupons)
        CompletableFuture<UserDTO> finalPageFuture =
                userDTOFuture.thenCombine(couponFuture, (dto, couponsList)->{
                   dto.coupons = couponsList;
                   return dto;
                });

        // 7. 异常处理示例A exceptionally(只在异常时兜底)
        CompletableFuture<Integer> safeScoreFuture =
                CompletableFuture.supplyAsync(demo::queryScoreWithPossibleFailure, IO_POOL)
                        .exceptionally((ex)->{
                            System.out.println("积分服务异常:" + ex.getMessage());
                            return 0;
                        });

        // 8. 异常处理实例B handle(成功 / 失败都统一处理)
        CompletableFuture<String> scoreMsgFuture =
                CompletableFuture.supplyAsync(demo::queryScoreWithPossibleFailure, IO_POOL)
                        .handle((score, ex)->{
                           if(ex != null) return "积分服务暂不可用";
                           return "积分:" + score;
                        });

        // 9. allOf:等待一组任务都完成(常用于打点 / 统计 / 日志)
        CompletableFuture<Void> allMainTaskDone =
                CompletableFuture.allOf(userFuture, scoreFuture, orderCountFuture, couponFuture);
        allMainTaskDone.thenRun(()-> System.out.println("[打点] user / score / order / coupon 四个任务全部完成"));

        // 10. thenAccept:异步回调"最终消费结果"(模拟渲染页面 / 返回接口)
        finalPageFuture.thenAccept((dto)->{
            long cost = System.currentTimeMillis() - start;
            System.out.println("\n=== 页面渲染 DTO(回调触发)===");
            System.out.println(dto);
            System.out.println("耗时约:" + cost + "ms");
        });

        // 11. 也可以 join 获取最终结果(会阻塞当前线程直到完成)
        // 注意:join抛 RuntimeException;get 抛受检异常
        UserDTO dto = finalPageFuture.join();
        System.out.println("\n=== 主线程 join 得到最终 DTO ===");
        System.out.println(dto);

        // 12. 看看异常处理示例的结果
        System.out.println("\n=== 异常处理示例输出 ===");
        System.out.println("safeScoreFuture = " + safeScoreFuture.join());
        System.out.println("scoreMsgFuture = " + scoreMsgFuture.join());



        // 关闭线程池(demo 必做,真实服务一般在shutdown hook中做)
        IO_POOL.shutdown();
    }
    
    
    /*--------------------------------------------------------------------------
    模拟:数据结构
    * (User / Coupon / DTO)
    * -------------------------------------------------------------------------*/
    static class User {
        final int id;
        final String name;

        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }

    static class Coupon { // 优惠券
        final String code;
        public Coupon(String code) {
            this.code = code;
        }
        @Override
        public String toString() {
            return code;
        }
    }
    // "UserDTO" 通常指 "User Data Transfer Object",即用户数据传输对象。
    // 在软件开发中,它是一种设计模式,用于在不同的层(比如表现层和业务逻辑层之间)传递数据。
    static class UserDTO {
        final int id;
        final String name;
        final String level;
        int orderCount;
        List<Coupon> coupons;
        public UserDTO(int id, String name, String level) {
            this.id = id;
            this.name = name;
            this.level = level;
        }

        @Override
        public String toString() {
            return "UserDTO{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", level='" + level + '\'' +
                    ", orderCount=" + orderCount +
                    ", coupons=" + coupons +
                    '}';
        }
    }
    /*--------------------------------------------------------------------------
    * 模拟:耗时查询方法(业务方法)
    * ------------------------------------------------------------------------*/

    User queryUser() {
        sleepMs(800);
        System.out.println(threadTag() + "queryUser done");
        return new User(1, "张三");
    }
    // 模拟查询积分(例如远程 RPC)
    // "远程 RPC" 指远程过程调用(Remote Procedure Call)。
    // 它是一种通过网络让程序调用另一个地址空间(通常是不同主机)里的过程或函数,无需开发者显式编写网络通信相关代码的技术。
    Integer queryScore() {
        sleepMs(1200);
        System.out.println(threadTag() + "queryScore done");
        return 88;
    }

    // 模拟查询订单数
    Integer queryOrderCount() {
        sleepMs(900);
        System.out.println(threadTag() + "queryOrderCount done");
        return 5;
    }

    // 模拟:先知道userID 才能查询优惠券(串行依赖场景)
    List<Coupon> queryCoupons(int userID) {
        sleepMs(700);
        System.out.println(threadTag() + "queryCoupons(userID= " + userID + ") done");
        // 模拟优惠券
        return IntStream.rangeClosed(1, 3)
                .mapToObj((i)-> new Coupon("CP-" + userID + "-" + i))
                .collect(Collectors.toList());
    }

    // 模拟:积分服务有一定概率失败,用于演示 execptionally / handle
    public Integer queryScoreWithPossibleFailure() {
        sleepMs(600);
        // 30%概率抛异常
        if(RND.nextInt(10) < 3) {
            throw new RuntimeException("score service timeout");
        }
        System.out.println(threadTag() + "queryScoreWithPossibleFailure done");
        return 90;
    }

    /*--------------------------------------------------------------------------
    * 工具方法
    * ------------------------------------------------------------------------*/
    static void sleepMs(long ms) {
        try {
            TimeUnit.MILLISECONDS.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 真实业务:通常应该恢复中断状态
        }
    }

    static String threadTag() {
        return "[" + Thread.currentThread().getName() + "] ";
    }
}

Future vs CompletableFuture 总对比

维度 Future CompletableFuture
异步执行
获取方式 阻塞get 非阻塞回调
支持回调
支持组合
异常处理
编程模型 命令式 声明式
底层 线程池 ForkJoinPool

如何选择Future / CompletableFuture

  • 用Future
    • 代码简单
    • 只关心最终结果
    • 老系统 / 兼容性需求
  • 用CompletableFuture
    • 多异步任务编排
    • IO / RPC / 微服务
    • 追求非阻塞、高并发

总结

Future 接口用于表示一个异步计算的结果,但只能通过阻塞或轮询的方式获取结果,缺乏回调和任务组合能力。CompletableFuture 在 Future 的基础上引入了异步回调、任务编排和函数式编程模型,使异步流程可以以非阻塞、链式的方式组织,极大提升了并发程序的可读性和扩展性。

相关推荐
派葛穆2 小时前
Python-PyQt5 安装与配置教程
开发语言·python·qt
万邦科技Lafite2 小时前
一键获取京东商品评论信息,item_reviewAPI接口指南
java·服务器·数据库·开放api·淘宝开放平台·京东开放平台
小乔的编程内容分享站2 小时前
记录使用VSCode调试含scanf()的C语言程序出现的两个问题
c语言·开发语言·笔记·vscode
indexsunny2 小时前
互联网大厂Java面试实战:从Spring Boot到微服务架构的技术问答解析
java·spring boot·redis·微服务·kafka·jwt·flyway
toooooop82 小时前
php BC MATH扩展函数计算精度-第三个参数
开发语言·php
蓁蓁啊2 小时前
C/C++编译链接全解析——gcc/g++与ld链接器使用误区
java·c语言·开发语言·c++·物联网
sheji34162 小时前
【开题答辩全过程】以 基于SpringBoot的疗养院管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
weixin_307779132 小时前
C#实现两个DocumentDB实例之间同步数据
开发语言·数据库·c#·云计算