JUC第一章 java中基础概念和CompletableFuture

JAVA中JUC多线程并发编程

第一章 java中基础概念和CompletableFuture


文章目录


一、线程基础知识

1.为什么多线程极其重要

硬件方面:摩尔定律失效

摩尔定律:

它是由英特尔创始人之一Gordon Moore(戈登·摩尔)提出来的。其内容为:

当价格不变时,集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍,性能也将提升一倍。

换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。

可是从2003年开始CPU主频已经不再翻倍,而是采用多核而不是更快的主频。

摩尔定律失效。

在主频不再提高且核数在不断增加的情况下,要想让程序更快就要用到并行或并发编程。

2.java中的线程

private native void start0();

Java语言本身底层就是C++语言:

大概执行过程: java线程是通过start的方法启动执行的,主要内容在native方法start0中,Openjdk的写JNI一般是一一对应的,Thread.java对应的就是Thread.c,start0其实就是JVM_StartThread。此时查看源代码可以看到在jvm.h中找到了声明,jvm.cpp中有实现。再到thread.cpp,最后发现调用os::start_thread(thread) :意思是最终确实会调用操作系统的原生 API 来创建真正的系统线程。(可以看OpenJDK源码)

3.相关概念

进程 :是程序的⼀次执⾏,是系统进⾏资源分配的最小单位,每⼀个进程都有它⾃⼰的内存空间和系统资源
线程 :在同⼀个进程内⼜可以执⾏多个任务,⽽这每⼀个任务我们就可以看做是⼀个线程,是调度的最小单元,⼀个进程会有1个或多个线程的
管程:Monitor是管程模型在编程语言(如 Java)或操作系统中的具体实现。(通常称为监视器或对象锁)Monitor其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象,Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。具体体现在 synchronized 关键字和 Object 类的 wait/notify 方法上,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持。

Java线程分为用户线程和守护线程 ,线程的daemon属性为true表示是守护线程,false表示是用户线程
用户线程 :是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程
守护线程:是系统的工作线程,它会完成这个程序需要完成的业务操作。当程序中所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出。设置守护线程,需要在start()方法之前进行

二、CompletableFuture

1.Callable 和 Future

Callable 和 Future 是 Java 并发编程中一对紧密协作的核心组件,它们共同解决了 Runnable 无法返回执行结果和处理检查型异常的局限。

关系:

当你将一个 Callable 任务提交给线程池(ExecutorService)后,线程池会立即返回一个 Future 对象。这个 Future 对象就像一个"取餐凭证",你可以在未来某个时刻用它来获取任务的执行结果。

Future 虽然解决了结果获取的问题,但它本身存在一些局限,比如不支持链式调用、难以组合多个任务等。

因此,Java 8 引入了 CompletableFuture,它是 Future 的增强版,提供了更强大的异步编程能力:

2.CompletableFuture

  • 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法。
  • 它可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作。
  • 它实现了Future和CompletionStage接口

简单代码:无返回值

java 复制代码
public class CompletableFutureDemo2
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
            //暂停几秒钟线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("-----task is over");
        });
        System.out.println(future.get());
    }
}

简单代码:有返回值

java 复制代码
public class CompletableFutureDemo2
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return ThreadLocalRandom.current().nextInt(100);
        });

        System.out.println(completableFuture.get());
    }
}

常用方法

  • 获得结果和触发计算:join()会阻塞当前线程,get()会阻塞当前线程complete()就是结束阻塞并get()获取设置的complete里面的值.
  • 对计算结果进行处理:thenApply()串行,handle()有异常也可以往下一步走,根据带的异常参数可以进一步处理
  • 对计算结果进行消费:thenAccept()
  • 对计算速度进行选用:applyToEither()谁快用谁
  • 对计算结果进行合并:thenCombine()两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine
    注意:还有其他好多方法,不一一列举了,xxxxAsync主要区别是用那个线程

3.实际应用

它的核心价值在于 :将串行执行的任务改为并行执行,从而大幅降低接口响应时间;或者将复杂的业务逻辑通过链式调用变得清晰易读。但是也要注意oom
聚合多个独立数据源(并行调用)

这是最经典、收益最明显的场景。

场景描述:在一个微服务架构的系统中,首页或详情页往往需要聚合多个下游服务的数据。

传统做法(串行):先查用户信息 -> 再查订单 -> 再查积分。总耗时 = 三者之和。

优化做法(并行):同时发起三个查询,最后等待结果聚合。总耗时 ≈ 最慢的那个查询。

java 复制代码
// 1. 异步发起三个独立查询
CompletableFuture<UserInfo> userFuture = CompletableFuture.supplyAsync(() -> userService.getUser(userId), executor);
CompletableFuture<List<Order>> ordersFuture = CompletableFuture.supplyAsync(() -> orderService.getOrders(userId), executor);
CompletableFuture<Integer> pointsFuture = CompletableFuture.supplyAsync(() -> pointsService.getPoints(userId), executor);

// 2. 等待所有任务完成并组合结果
CompletableFuture<UserProfile> result = CompletableFuture.allOf(userFuture, ordersFuture, pointsFuture)
    .thenApply(v -> {
        UserProfile profile = new UserProfile();
        profile.setUser(userFuture.join()); // 此时join不会阻塞,因为已经确定完成了
        profile.setOrders(ordersFuture.join());
        profile.setPoints(pointsFuture.join());
        return profile;
    });

异步任务链式处理(流水线)

场景描述:业务逻辑有明显的先后依赖关系,前一步的输出是后一步的输入。(这个只是举个例子,实际的电商更复杂)

例子:用户下单流程 -> 1.校验库存 -> 2.扣减余额 -> 3.生成订单 -> 4.发送通知。

java 复制代码
CompletableFuture.supplyAsync(() -> {
    // 步骤1:校验库存
    return inventoryService.check(stockId);
}, executor).thenCompose(isValid -> {
    // 步骤2:如果校验通过,执行下一个异步任务(扣减余额)
    // thenCompose 用于连接两个有依赖关系的 Future
    return balanceService.deduct(userId, amount);
}, executor).thenAccept(balanceResult -> {
    // 步骤3:消费结果(如记录日志或发送MQ),无返回值
    log.info("扣款成功: {}", balanceResult);
}).exceptionally(ex -> {
    // 统一异常处理
    log.error("下单流程失败", ex);
    return null;
});

多策略"竞速"与超时熔断

场景描述:为了保证高可用或低延迟,同时调用多个渠道,谁快用谁;或者防止第三方接口卡死导致系统雪崩。

例子1(竞速):同时调用"高德地图API"和"百度地图API",哪个先返回路线就用哪个。

例子2(超时):给一个耗时任务设置一个"超时闹钟"。

java 复制代码
CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
    return thirdPartyApi.call(); // 模拟耗时
}, executor);

// 设置超时:如果 3秒 内没完成,则执行 fallback 逻辑
String result = task.orTimeout(3, TimeUnit.SECONDS)
    .exceptionally(ex -> {
        return "默认降级数据"; // 超时或异常时返回兜底数据
    }).join();

耗时操作的"即返"模式(解耦)

场景描述:用户发起一个非常耗时的操作(如导出报表、生成复杂PDF、大文件上传),不能让前端一直转圈等待。

做法:Controller 层立即返回"任务已接收",后台异步执行任务,完成后通过消息通知或轮询接口告知用户。

java 复制代码
// Controller 层直接返回,不等待
CompletableFuture.runAsync(() -> {
    try {
        exportService.exportLargeData(taskId); // 耗时操作
        notificationService.sendSuccess(taskId); // 完成后通知
    } catch (Exception e) {
        notificationService.sendError(taskId); // 失败通知
    }
}, executor);

return Result.success("任务已提交,请稍后查看进度");

制造执行系统(MES)或物联网(IoT)场景

场景描述:在工业场景中,需要实时处理设备状态、库存、订单等多个维度的并发数据。

例子:在分配生产任务时,需要同时检查"设备是否空闲"、"原材料是否充足"、"订单优先级"三个条件,全部满足才下发指令。

java 复制代码
CompletableFuture.allOf(
    queryDeviceStatus(deviceId),
    queryMaterialStock(materialId),
    queryOrderPriority(orderId)
).thenRun(() -> {
    // 只有当三个条件都查询完毕后,才执行调度逻辑
    if (isDeviceOk && isMaterialOk) {
        schedulingService.assignTask();
    }
});

生产环境避坑指南(最佳实践)

在实际业务中使用 CompletableFuture 时,请务必注意以下几点,否则容易引发线上故障:

  • 必须使用自定义线程池
    原因:CompletableFuture 默认使用 ForkJoinPool.commonPool(),其核心线程数默认等于 CPU 核数。如果你的业务是 IO 密集型(如查库、调接口),很容易耗尽线程池,导致整个系统卡死。
    做法:始终使用 supplyAsync(supplier, executor) 并传入自定义的 ThreadPoolExecutor。
  • 注意异常处理
    异步线程中的异常不会自动抛到主线程,必须使用 exceptionally、handle 或 whenComplete 进行捕获和处理,否则异常会被"吞掉",导致排查问题困难。
  • 避免过度嵌套
    虽然 thenCompose 可以嵌套,但过深的嵌套(回调地狱)会降低代码可读性。如果逻辑过于复杂,建议拆分成独立的方法。
  • Web 上下文丢失
    在 Spring 中,异步线程无法直接继承主线程的 RequestAttributes 或 ThreadLocal 变量(如用户信息、TraceId)。需要在异步执行前手动传递这些上下文。
相关推荐
迷藏4942 小时前
**超融合架构下的Go语言实践:从零搭建高性能容器化微服务集群**在现代云原生时代,*
java·python·云原生·架构·golang
それども2 小时前
Spring Bean @Autowired自注入空指针问题
java·开发语言·spring
如来神掌十八式2 小时前
Java所有的锁:从基础到进阶
java·
硅基诗人2 小时前
Java后端高并发核心瓶颈突破(JVM+并发+分布式底层实战)
java·jvm·分布式
聆听。。花开雨落2 小时前
intelij idea闪退后再启动tomcat报错端口冲突
java·tomcat·intellij-idea
Java面试题总结2 小时前
Spring Boot 包扫描新姿势:AutoScan vs @Import vs @ComponentScan 深度对比
java·数据库·spring boot
掘金者阿豪2 小时前
数据库安全第一关:用户密码存储与认证机制的深度拆解
java·前端·后端
花千树-0102 小时前
McpAgentExecutor 混合挂载:HTTP 工具与 NPX 服务器同时接入同一 Agent
java·agent·function call·spring ai·mcp·toolcall·java ai
XiYang-DING2 小时前
【Java】反射
java·开发语言