Java 异步编程

什么是异步?为什么工作中必须用?

  1. 同步 VS 异步
同步:你去吃饭,必须等饭做好、端上来,你才能做别的事(阻塞等待)。
异步:你点餐后,去玩手机、聊天,饭好了服务员通知你(不阻塞,后台执行)。
  1. 工作中为什么要用异步?(核心价值)
1)提升接口响应速度:比如用户注册 → 发短信 / 邮件 → 记录日志,同步要等所有步骤完成,异步只需要执行核心注册逻辑。
2)提高系统吞吐量:不阻塞线程,能处理更多请求。
3)解耦业务逻辑:核心流程和非核心流程分离。
4)避免线程阻塞:比如调用第三方接口、IO 操作(文件、数据库),非常适合异步。

一、Java异步编程的4种核心实现方式

从简单到高级,企业 99% 场景都用这 4 种:

1、基础:Thread + 匿名内部类

2、进阶:Runnable + 线程池

3、标准:Future(JDK 1.5)

4、推荐:CompletableFuture(JDK 1.8+,工作主力)

二、异步编程基础 —— Thread + Runnable

2.1 核心概念

Runnable:无返回值、无异常抛出的异步任务接口。
Thread:JVM 线程,直接启动异步执行。

2.2 代码实现(最基础代码)

public class AsyncDemo {
    public static void main(String[] args) {
        System.out.println("主线程开始:" + Thread.currentThread().getName());

        // 1. 创建异步任务
        Runnable task = () -> {
            System.out.println("异步任务执行:" + Thread.currentThread().getName());
            // 模拟业务:发短信、写日志、调用第三方接口
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务执行完成");
        };

        // 2. 启动异步线程
        new Thread(task).start(); 

        System.out.println("主线程结束,不用等待异步任务");
    }
}

2.3 关键细节

1、start() 和 run() 区别

start():真正启动新线程,异步执行。

run():只是普通方法调用,同步执行,不是异步!

2、缺点:

无返回值

无法处理异常

频繁创建线程耗资源,工作中绝对禁止直接用 new Thread

三、工作基础 —— 线程池 + Runnable

工作中严禁手动创建线程,必须用线程池!

3.1 为什么要用线程池?

1、复用线程,减少创建 / 销毁开销

2、控制并发数量,避免服务器崩溃

3、统一管理线程生命周期

3.2 JDK 提供的线程池(工作常用)

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolAsyncDemo {
    public static void main(String[] args) {
        // 1. 创建线程池(工作推荐自定义,不推荐 Executors)
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        System.out.println("主线程:" + Thread.currentThread().getName());

        // 2. 提交异步任务
        threadPool.execute(() -> {
            System.out.println("线程池异步任务:" + Thread.currentThread().getName());
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            System.out.println("异步任务完成");
        });

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

        // 3. 关闭线程池(项目中是全局单例,不用关闭)
        threadPool.shutdown();
    }
}

3.3 关键细节
1、execute() vs submit()

  • execute():无返回值,只能执行 Runnable

  • submit():有返回值 Future,可获取结果

2、工作禁止用 Executors 快捷创建线程池!

  • newFixedThreadPool:队列无限长,OOM

  • newCachedThreadPool:线程无限创建,OOM

3、工作标准写法:自定义线程池

import java.util.concurrent.*;

public class CustomThreadPool {
    // 全局单例线程池(Spring 项目可交给容器管理)
    public static final ExecutorService ASYNC_POOL = new ThreadPoolExecutor(
            5,                  // 核心线程
            10,                 // 最大线程
            60L,                // 空闲时间
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(100),  // 有界队列(关键!)
            new ThreadFactory() {           // 自定义线程名(排错神器)
                final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = defaultFactory.newThread(r);
                    thread.setName("async-work-thread-" + thread.getId());
                    return thread;
                }
            },
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
    );
}

拒绝策略说明:

  • CallerRunsPolicy:主线程执行(最安全,不丢任务)

  • AbortPolicy:抛异常(默认)

  • DiscardPolicy:丢弃任务

  • DiscardOldestPolicy:丢弃最老任务

四、带返回值的异步 —— Future

4.1 核心作用

解决 Runnable 无返回值的问题,可以获取异步任务执行结果。

4.2 代码实战演示

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

public class FutureDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService pool = CustomThreadPool.ASYNC_POOL;

        System.out.println("主线程开始");

        // 1. 提交有返回值的任务
        Future<String> future = pool.submit(() -> {
            System.out.println("异步任务执行");
            Thread.sleep(1000);
            return "异步任务执行结果:success";
        });

        System.out.println("主线程做其他事...");

        // 2. 阻塞获取结果(注意:会阻塞!)
        String result = future.get();
        System.out.println("获取异步结果:" + result);

        pool.shutdown();
    }
}

4.3 Future关键细节

1、get() 会阻塞主线程
调用 get() 时,如果任务没执行完,主线程会一直等 → 变成伪异步!
2、get(timeout, unit) 必须加超时时间
// 最多等 500ms,超时抛异常
future.get(500, TimeUnit.MILLISECONDS);
3、无法监听任务完成
4、不支持链式调用、多任务组合
所以:Future 工作中很少直接用,只做过渡了解。

五、工作主力 —— CompletableFuture(终极方案)

JDK 1.8 推出,真正的异步编程神器,解决 Future 所有痛点:

  • 支持回调,不阻塞

  • 支持链式调用

  • 支持多任务组合(串行、并行、汇聚)

  • 支持异常处理

  • 支持自定义线程池

5.1 创建 CompletableFuture 的 4 种方法

// 1. 无返回值,使用默认线程池
CompletableFuture.runAsync(() -> {});

// 2. 有返回值,使用默认线程池
CompletableFuture.supplyAsync(() -> "返回值");

// 3. 无返回值 + 自定义线程池(工作推荐!)
CompletableFuture.runAsync(() -> {}, 自定义线程池);

// 4. 有返回值 + 自定义线程池(工作最常用!)
CompletableFuture.supplyAsync(() -> "结果", 自定义线程池);

5.2 核心API细节讲解(工作高频)

5.2.1 任务完成回调:thenAccept /thenApply

  • thenAccept:接收上一步结果,无返回值

  • thenApply:接收上一步结果,有返回值

CompletableFuture.supplyAsync(() -> {
    return "用户ID:1001";
}, CustomThreadPool.ASYNC_POOL)
.thenApply(result -> {
    // 处理结果,有返回值
    return result + " → 发送短信";
})
.thenAccept(result -> {
    // 处理结果,无返回值
    System.out.println("最终结果:" + result);
});

5.2.2 异步继续执行:

thenApplyAsync /thenAcceptAsync

加 Async 表示下一步继续用线程池线程执行,不占用主线程。

5.2.3 任务完成后处理:whenComplete

无论成功 / 异常,都会执行:

.whenComplete((result, ex) -> {
    if(ex != null) {
        System.out.println("异常:" + ex.getMessage());
    } else {
        System.out.println("成功:" + result);
    }
});

5.2.4 异常捕获:exceptionally

相当于 try-catch:

.exceptionally(ex -> {
    System.out.println("异步任务异常:" + ex.getMessage());
    return "默认降级结果";
});

5.2.5 工作最强:多任务组合

① 串行执行(A → B → C)

CompletableFuture
.supplyAsync(任务A)
.thenApplyAsync(任务B)
.thenApplyAsync(任务C);

② 并行执行 + 等待全部完成(allOf)

CompletableFuture<Void> allTask = CompletableFuture.allOf(
    CompletableFuture.runAsync(任务1),
    CompletableFuture.runAsync(任务2),
    CompletableFuture.runAsync(任务3)
);

// 等待所有任务完成
allTask.join();

③ 并行执行 + 任意一个完成就返回(anyOf)

CompletableFuture<Object> anyTask = CompletableFuture.anyOf(任务1,任务2);
Object result = anyTask.join();

5.3 工作实战:用户注册异步优化(真实业务)
需求

用户注册接口:

  1. 保存用户信息(核心,必须同步)

  2. 发送短信(异步)

  3. 发送邮件(异步)

  4. 记录日志(异步)

  5. 赠送优惠券(异步)

同步执行:总耗时 = 1+1+1+1 = 4 秒

异步执行:总耗时 = 1 秒

实战代码:

@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    // 注入我们自定义的全局线程池
    @Resource
    private ExecutorService asyncPool;

    public String register(User user) {
        long start = System.currentTimeMillis();
        System.out.println("注册开始,线程:" + Thread.currentThread().getName());

        // 1. 核心逻辑:保存用户(同步)
        userMapper.insert(user);

        // 2. 异步执行非核心任务
        CompletableFuture<Void> smsTask = CompletableFuture.runAsync(() -> {
            sendSms(user.getPhone());
        }, asyncPool);

        CompletableFuture<Void> emailTask = CompletableFuture.runAsync(() -> {
            sendEmail(user.getEmail());
        }, asyncPool);

        CompletableFuture<Void> logTask = CompletableFuture.runAsync(() -> {
            saveLog(user.getId());
        }, asyncPool);

        CompletableFuture<Void> couponTask = CompletableFuture.runAsync(() -> {
            giveCoupon(user.getId());
        }, asyncPool);

        // 3. 组合所有异步任务(可选等待)
        CompletableFuture.allOf(smsTask, emailTask, logTask, couponTask).join();

        System.out.println("注册完成,耗时:" + (System.currentTimeMillis() - start) + "ms");
        return "注册成功";
    }

    // 发短信
    private void sendSms(String phone) {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("短信发送成功:" + phone);
    }

    // 发邮件
    private void sendEmail(String email) {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("邮件发送成功:" + email);
    }

    // 保存日志
    private void saveLog(Long userId) {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("日志保存成功:" + userId);
    }

    // 赠送优惠券
    private void giveCoupon(Long userId) {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("优惠券赠送成功:" + userId);
    }
}

六、Spring 中异步 —— @Async 注解

工作中 SpringBoot 项目最简单的异步用法,底层就是线程池 + AOP。

6.1 使用步骤

1、启动类开启异步

@SpringBootApplication
@EnableAsync // 开启异步
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

2、方法上加 @Async

@Service
public class AsyncService {

    // 异步方法
    @Async("asyncPool") // 指定线程池名称
    public void sendSms(String phone) {
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        System.out.println("异步发短信:" + phone);
    }
}

3、配置自定义线程池

@Configuration
public class ThreadPoolConfig {
    @Bean("asyncPool")
    public Executor asyncPool() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, 10, 60L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(100),
            new CustomThreadFactory("async"),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
        return executor;
    }
}

6.2 @Async 关键坑点(必看,否则异步失效)

  1. 必须用 Spring 注入的对象调用,不能内部 this.方法()

  2. 方法不能是 private /static/final

  3. 必须加 @EnableAsync

  4. 避免事务失效:异步方法和事务方法不要混用

七、异步编程必须注意的坑(工作保命)

7.1 异步线程上下文丢失

主线程的 RequestAttributes、ThreadLocal 无法传递到异步线程

解决方案:手动传递上下文

// 手动传递 RequestContext
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

CompletableFuture.runAsync(() -> {
    RequestContextHolder.setRequestAttributes(attributes);
    // 执行业务
}, asyncPool);

7.2 异步线程异常吞没

  • 异步线程抛出异常,主线程感知不到

  • 必须用 exceptionally / whenComplete 捕获

7.3 线程池参数不合理导致问题

  • 核心线程太小:任务堆积

  • 最大线程太大:CPU 飙升

  • 队列无界:OOM 内存溢出

7.4 主线程结束,异步任务中断

比如测试方法 main 函数,主线程结束,异步线程会被杀死

解决方案:join() / get() 等待,或使用守护线程

7.5 异步导致数据不一致

  • 比如:用户注册成功,短信发送失败

  • 解决方案:重试机制 + 消息队列(RabbitMQ/RocketMQ) 保证最终一致性

八、异步编程工作最佳实践

1、所有异步必须用自定义线程池,禁止用 Executors 和 new Thread ()
2、线程必须自定义名称,方便排查问题
3、异步方法必须捕获异常,记录日志
4、耗时 IO 操作(HTTP、DB、MQ、文件)全部异步化
5、核心流程同步,非核心流程异步
6、CompletableFuture 优先于 @Async,更灵活
7、高并发场景用消息队列替代异步,保证可靠性

整体总结:

基础异步:Thread + Runnable → 工作禁用
标准异步:线程池 + Future → 有返回值但会阻塞
主力异步:CompletableFuture → 工作 90% 场景首选
简化异步:@Async → Spring 项目快速实现
核心原则:自定义线程池、不阻塞、捕获异常、上下文安全
最终目标:提升接口响应速度、提高系统吞吐量、解耦业务
posted @ 2026-05-31 16:25  爱的万龙决  阅读(15)  评论(0)    收藏  举报