Java—异步编程

一、同步 vs 异步

概念:

同步: 任务一个个做,做完一个才能做下一个。比如烧水--->洗杯子--->泡茶,必须等水烧开才能泡;

异步: 任务同时进行。烧水的同时可以洗杯子,水烧好后再回来泡茶;

Java同步示例(阻塞):

java 复制代码
/**
 * Java同步
 */
public class SyncExample {
    public static void main(String[] args) {
        System.out.println("开始准备煮饭");

//        洗米需要3s(模拟)
        washRice();

//        煮饭需要20秒(模拟)
        cookRice();

        System.out.println("饭煮好了,可以吃饭了!");

    }

    //    洗米操作,需要3秒
    static void washRice() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("洗完米");
    }

    //    煮饭操作,需要20秒
    static void cookRice() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
        }
        System.out.println("饭熟了");
    }

}

//开始煮饭------洗完米------饭熟了------饭煮好了,可以吃饭了!(期间啥也做不了)

Java异步简单示例:(用新线程)

java 复制代码
/**
 * Java异步
 */
public class AsyncExample {
    public static void main(String[] args) {
        System.out.println("开始准备煮饭");

//        异步煮饭:新线程去做,主线程继续
        new Thread(() -> {
            cookRice();    //耗时操作
            System.out.println("饭熟了");
        }).start();

//        主线程继续做其他事
        washRice();
        System.out.println("洗完米,我可以先炒菜...");

//        防止主线程提前结束
        try {
            Thread.sleep(25000);
        } catch (InterruptedException e) {
        }

    }

    //    洗米
    static void washRice() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("洗完米");
    }

    //    做饭操作
    static void cookRice() {
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
        }
        System.out.println("饭熟了(异步)");
    }
}

关键点: 异步不阻塞主线程,能同时做多件事;

二、基础异步工具

概念:

  • Thread: 线程,就是一个执行路径;
  • Runnable: 要执行的任务(函数式接口);

创建异步任务的三种方式:

java 复制代码
/**
 * 方式1:继承Thread
 */
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("任务1执行中。。。。。。");
    }
}

/**
 * 方式2:实现Runnable接口(推荐)
 */
class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("任务2执行中。。。。。。");
    }
}


//测试类
class Test {
    public static void main(String[] args) {
//        方式1:继承Thread
        new MyThread().start();
//        方式2:实现Runnable接口
        MyThread2 task = new MyThread2();
        new Thread(task).start();
//        方式3:匿名内部类
        new Thread(() -> {
            System.out.println("任务3执行中。。。。。。");
        }).start();

    }
}

实战:模拟下载文件后处理

java 复制代码
/**
 * 模拟下载文件后处理
 */
public class DownloadExample {
    public static void main(String[] args) {
        System.out.println("开始下载文件");

        //异步下载
        new Thread(() -> {
            downloadFile("大文件.zip");
            //下载完后处理(注意:这还在异步线程里)
            processFile("大文件.zip");
        }).start();
        System.out.println("下载已开始,我去做别的事了");
        doOtherWork();
    }

    static void downloadFile(String file) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        System.out.println(file + "下载完成");
    }

    static void processFile(String file) {
        System.out.println("开始处理" + file);
    }

    static void doOtherWork() {
        System.out.println("浏览网页");
    }

}

返回结果:

bash 复制代码
开始下载文件
下载已开始,我去做别的事了
浏览网页
大文件.zip下载完成
开始处理大文件.zip

痛点: 如果需要在下载完成后把结果返回给主线程,用Thread很难做到。

三、Future --- 获取异步结果

概念:

Future是一个凭证,代表未来的某个结果。你可以:

  • 继续做其他事;
  • 需要结果时,调用get()等待异步任务完成并取回结果;

基本使用:

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class FutureExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

//        提交一个异步任务,返回Future
        Future<Integer> future = executor.submit(() -> {
            Thread.sleep(2000);  //模拟计算
            return 100 + 200;
        });

        System.out.println("异步计算已提交,我先去做别的事");
        Thread.sleep(1000);

//        需要结果时,get()会阻塞直到结果准备好
        Integer result = future.get();  //这里会的古代最多2秒
        System.out.println("结果是:" + result);
        executor.shutdown();
    }
}

Future的局限(为什么不够好)

java 复制代码
// 问题1:get()会阻塞线程,失去了异步的意义
Integer result = future.get();  // 等着吧,做不了别的事

// 问题2:无法链式操作(做完后自动触发另一个任务)
// 比如:下载文件 -> 自动解压 -> 自动删除原文件,很难用Future串联

// 问题3:多个异步任务组合很麻烦

四、CompletableFuture ---真正的异步神器

概念:

CompletableFuture实现了Future,但可以非阻塞地链式调用,像流水一样组合异步任务。

4.1 创建异步任务

java 复制代码
import java.util.concurrent.CompletableFuture;

public class CompletableFutureBasic {
    public static void main(String[] args) throws Exception {
//        方式1:runAsync - 无返回值
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println("异步执行,无返回值");
        });

//        方式2:supplyAsync - 有返回值
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            return "Hello,Async!";
        });

//        获取结果(会阻塞,但通常不用,而是用thenApply等)
        System.out.println(future2.get());

//        避免主线程退出
        Thread.sleep(1000);
    }
}

返回结果:

bash 复制代码
异步执行,无返回值
Hello,Async!

4.2 thenApply - 转换结果

java 复制代码
import java.util.concurrent.CompletableFuture;

public class CompletableFutureBasic {
    public static void main(String[] args) throws Exception {
        CompletableFuture.supplyAsync(() -> {
            return "hello";
        }).thenApply(result -> {
            return result.toUpperCase();
        }).thenAccept(finalResult -> {
            System.out.println(finalResult);   //输出:HELLO
        });
    }
}

4.3 thenAccept - 消费结果(无返回值)

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest {
    public static void main(String[] args) {
//       thenAccept - 消费结果(无返回值)
        CompletableFuture.supplyAsync(() -> 100)
                .thenApply(x -> x * 2)
                .thenAccept(result -> System.out.println("结果是:" + result));  //结果是:200
    }
}

4.4 thenRun - 完成任务后执行(不关心结果)

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest {
    public static void main(String[] args) {
//       thenRun - 完成任务后执行(不关心结果)
        CompletableFuture.supplyAsync(() -> "数据保存成功")
                .thenRun(() -> System.out.println("发送通知邮件"));   //发送通知邮件
    }
}

五、 组合多个异步任务

5.1 thenCompose - 任务依赖(前一个结果作为输入)

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest02 {
    public static void main(String[] args) {
        // 场景:根据用户ID获取用户详情(两步都异步)
        CompletableFuture.supplyAsync(() -> "user123")
                .thenCompose(userId -> getUserDetail(userId))  // 扁平化,避免Future嵌套
                .thenAccept(user -> System.out.println(user));
    }
    static CompletableFuture<String> getUserDetail(String userId) {
        return CompletableFuture.supplyAsync(() -> userId + " 的详细信息");
    }
}

5.2 thenCombine - 合并两个独立任务的结果

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest02 {
    public static void main(String[] args) {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);

        future1.thenCombine(future2, (result1, result2) -> result1 + result2)
                .thenAccept(sum -> System.out.println("和是:" + sum));  // 输出 30
    }

}

5.3 allOf / anyOf - 等待多个任务

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest02 {
    public static void main(String[] args) {
        CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "任务1");
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "任务2");
        CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> "任务3");

// 等待所有任务完成
        CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
        all.thenRun(() -> System.out.println("所有任务完成"));

// 等待任意一个任务完成
        CompletableFuture<Object> any = CompletableFuture.anyOf(f1, f2, f3);
        any.thenAccept(result -> System.out.println("最先完成的任务结果:" + result));
    }

}

六、异常处理

6.1 exceptionally - 处理异常并返回默认值

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest03 {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            if (Math.random() > 0.5) throw new RuntimeException("出错了");
            return "成功";
        }).exceptionally(ex -> {
            System.out.println("捕获异常:" + ex.getMessage());
            return "默认值";
        }).thenAccept(System.out::println);
    }
}

6.2 handle - 无论成功或失败都处理

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ThenTest03 {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "hello")
                .handle((result, ex) -> {
                    if (ex != null) {
                        return "错误:" + ex.getMessage();
                    }
                    return result.toUpperCase();
                })
                .thenAccept(System.out::println);
    }
}

七、实战综合案例

场景: 从两个服务异步获取用户信息和订单信息,组合后计算总金额,最后发送通知。

java 复制代码
/**
 * 从两个服务异步获取用户信息和订单信息,组合后计算总金额,最后发送通知。
 */
public class RealWorldExample {
    public static void main(String[] args) {
        CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> getUserInfo("123"));
        CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> getOrders("123"));

        userFuture.thenCombine(orderFuture, (user, orders) -> {   //合并两个独立任务的结果
            double total = orders.stream().mapToDouble(Order::getAmount).sum();
            return new Bill(user, total);
        }).thenApply(bill -> {     //转换结果
            System.out.println("生成账单:" + bill);
            return bill;
        }).thenAccept(bill -> {   //消费结果(无返回值)
            sendEmail(bill);
        }).exceptionally(ex -> {
            System.err.println("处理失败:" + ex);
            return null;
        });

        // 防止主线程退出
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
    }

    static User getUserInfo(String id) {
        sleep(1000);
        return new User(id, "张三");
    }

    static List<Order> getOrders(String userId) {
        sleep(1200);
        return Arrays.asList(new Order(100), new Order(50));
    }

    static void sendEmail(Bill bill) {
        System.out.println("发送账单邮件给:" + bill.user.name);
    }

    static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
        }
    }
}

class User {
    String id, name;

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

class Order {
    double amount;

    Order(double amount) {
        this.amount = amount;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }
}

class Bill {
    User user;
    double total;

    Bill(User user, double total) {
        this.user = user;
        this.total = total;
    }
}

八、思维导图

相关推荐
GIS数据转换器1 小时前
智慧能源管理平台
java·大数据·运维·人工智能·无人机
garmin Chen1 小时前
LeetcodeHot100打卡(14、合并空间,15、轮转数组,16、除了自身以外数组乘积,17.缺失的第一个整数)
java·笔记·学习·算法
接着奏乐接着舞1 小时前
dto 转entity方法
java·开发语言
我命由我123451 小时前
Android 开发问题:项目同时引入了两个包含相同类文件的库(AndroidX 库、旧版本支持库),导致了重复类错误
android·java·java-ee·android studio·android-studio·androidx·android runtime
0x00071 小时前
译 Anders Hejlsberg 谈 C# 与 .NET
开发语言·c#·.net
梓色系1 小时前
Spring AI 实战:从零搭建 MCP 客户端与服务端,让大模型拥有“手脚“
java·人工智能·spring
czhaii1 小时前
基于51单片机的Modbus从机通信系统
开发语言·单片机
elseif1232 小时前
【C++】vector 详细版
开发语言·c++·算法
秦时星星2 小时前
Spring AI + FastMCP 跨语言集成踩坑实录
java·人工智能·spring