多线程之CompletableFuture用法详解

JAVA支持的多线程开启方式

根据Oracle官方出具的Java文档说明,创建线程的方式只有两种:继承Thread或者实现Runnable接口。 但是这两种方法都存在一个缺陷,没有返回值,也就是说我们无法得知线程执行结果。虽然简单场景下已经满足,但是当我们需要返回值的时候怎么办呢? Java 1.5 以后的Callable和Future接口就解决了这个问题,我们可以通过向线程池提交一个Callable来获取一个包含返回值的Future对象,从此,我们的程序逻辑就不再是同步顺序。

Future接口理论知识复习

http://t.csdnimg.cn/KbbfGicon-default.png?t=N7T8http://t.csdnimg.cn/KbbfG

  • Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果,取消任务,判断任务是否被取消,判断任务是否完毕等
  • 比如主线程让一个子线程取执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程去做其他事情了,过了一会才去获取子任务的执行结果或变更的任务状态
  • 一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力地复杂业务

Future接口能干什么
Future是Java5新加的一个接口,提供了一种一部并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果
Runnable接口
Callable接口
Runnable重新run方法,Callable重写call方法;Runnable无返回值,Callable有返回值;Runnable不能抛异常,Callable能抛异常
callable和runnable都可以应用于executors。而thread类只支持runnable及其子类.

                        
原文链接:https://blog.csdn.net/qq_44447372/article/details/125123576

CountDownLatch介绍和使用【Java多线程必备】

参考链接http://t.csdnimg.cn/zKlLg

 CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。

特性

1. CountDownLatch 可以用于控制一个或多个线程等待多个任务完成后再执行。

2. CountDownLatch 的计数器只能够被减少,不能够被增加。

3. CountDownLatch 的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。

适用场景


1. 主线程等待多个子线程完成任务后再继续执行。例如:一个大型的任务需要被拆分成多个子任务并交由多个线程并行处理,等所有子任务都完成后再将处理结果进行合并。

2. 启动多个线程并发执行任务,等待所有线程执行完毕后进行结果汇总。例如:在一个并发请求量比较大的 Web 服务中,可以使用 CountDownLatch 控制多个线程同时处理请求,等待所有线程处理完毕后将结果进行汇总。

3. 线程 A 等待线程 B 执行完某个任务后再执行自己的任务。例如:在多线程中,一个节点需要等待其他节点的加入后才能执行某个任务,可以使用 CountDownLatch 控制节点的加入,等所有节点都加入完成后再执行任务。

4. 多个线程等待一个共享资源的初始化完成后再进行操作。例如:在某个资源初始化较慢的系统中,可以使用 CountDownLatch 控制多个线程等待共享资源初始化完成后再进行操作。

CountDownLatch 适用于多线程任务的协同处理场景,能够有效提升多线程任务的执行效率,同时也能够降低多线程任务的复杂度和出错率。

案例一

    (1) 场景

    一个简单的 CountDownLatch 示例,演示了如何使用 CountDownLatch 实现多个线程的同步。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
 
/**
 * CountDownLatchCase1
 * 如何使用CountDownLatch实现多个线程的同步。
 *
 * @author wxy
 * @since 2023-04-18
 */
public class CountDownLatchCase1 {
    private static final Logger LOGGER = LoggerFactory.getLogger(CountDownLatchCase1.class);
 
    public static void main(String[] args) throws InterruptedException {
        // 创建 CountDownLatch 对象,需要等待 3 个线程完成任务
        CountDownLatch latch = new CountDownLatch(3);
 
        // 创建 3 个线程
        Worker worker1 = new Worker(latch, "worker1");
        Worker worker2 = new Worker(latch, "worker2");
        Worker worker3 = new Worker(latch, "worker3");
 
        // 启动 3 个线程
        worker1.start();
        worker2.start();
        worker3.start();
 
        // 等待 3 个线程完成任务
        latch.await();
 
        // 所有线程完成任务后,执行下面的代码
        LOGGER.info("All workers have finished their jobs!");
    }
}
 
class Worker extends Thread {
    private static final Logger LOGGER = LoggerFactory.getLogger(Worker.class);
 
    private final CountDownLatch latch;
 
    public String name;
 
    public Worker(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }
 
    @Override
    public void run() {
        try {
            // 模拟任务耗时
            TimeUnit.MILLISECONDS.sleep(1000);
            LOGGER.info("{} has finished the job!", name);
        } catch (InterruptedException e) {
            LOGGER.error(e.getMessage(), e);
        } finally {
            // 一定要保证每个线程执行完毕或者异常后调用countDown()方法
            // 如果不调用会导致其他线程一直等待, 无法继续执行
            // 建议放在finally代码块中, 防止异常情况下未调用countDown()方法
            latch.countDown();
        }
    }
}

 主线程在等待子线程运行完之后再运行,如果不用countdown时,主线程就不等待子线程运行完之后再运行

 案例二

    (1) 场景

    当年刚工作不久,遇到一个这样的问题:远程调用某个api,大部分情况下需要2-3s才能读取到响应值。我需要解析响应的JSON用于后续的操作。由于这个调用是异步的,我没办法在主线程获取到响应的JSON值。

    当时第一时间想到的是让主线程休眠,但是休眠多久好呢?1、2、3s?显然是不行的,如果1s就请求成功并响应了,你要等3s,这不是浪费时间吗!使用countdownlatch

 

 案例

最多等到10秒,10秒子线程还没运行完,主线程就继续往下运行,如果不到10秒子线程运行完了,主线程也往下继续运行

 

ComplatableFuture为什么出现

get()方法在Future计算完成之前会一直处于阻塞状态
isDone()方法容易耗费CPU资源
对于真正的异步任务我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果.
阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源.因此设计出ComplatableFuture
ComplatableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方

 有多少组组合,计数器就记为多少,用的是 有返回值的方法,如果有返回值,泛型写返回值的类型(对于下面这个方法可以不用有返回值的方法),然后计数器- 1。最多等待2秒

// 使用线程安全的集合
        List<CouponDiscountDTO> dtos =
                Collections.synchronizedList(new ArrayList<>(solutions.size()));
        CountDownLatch latch = new CountDownLatch(solutions.size());
        for (List<Coupon> solution : solutions) {
            CompletableFuture.supplyAsync(new Supplier<CouponDiscountDTO>() {
                @Override
                public CouponDiscountDTO get() {
                    CouponDiscountDTO dto = calculateSolutionDiscount(availableCouponMap,orderCourses,solution);
                    return dto;
                }
            },discountSolutionExecutor).thenAccept(new Consumer<CouponDiscountDTO>() {   // 上面return的dto就是下面的参数
                @Override
                public void accept(CouponDiscountDTO dto) {
                    dtos.add(dto);
                    latch.countDown(); // 计数器减一
                }
            });
            try {
                latch.await(2, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                log.error("多线程计算优惠明细报错!!!");
            }
        }

 

CompletableFutureJava8引入的一个异步编程工具类,用于简化异步编程,提高代码可读性和可维护性。它可以让你在一个线程中进行异步编程,而无需显式地创建线程或使用回调函数。 CompletableFuture可以看作是对Future的扩展,它支持链式调用和组合操作,可以将多个CompletableFuture组合在一起,形成一个复杂的异步操作流程。它也提供了一些方法,比如thenApply、thenAccept、thenRun等,用于处理异步操作的结果。 在使用CompletableFuture时,我们可以将异步操作放在一个CompletableFuture对象中,然后通过链式调用的方式,将多个异步操作组合在一起,形成一个异步操作流程。当所有的异步操作都执行完毕后,可以使用CompletableFuture的get方法,获取异步操作的结果。 下面是一个使用CompletableFuture的示例代码: ```java CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { // 异步操作 return 10; }).thenApplyAsync(result -> { // 处理异步操作结果 return result * 2; }).thenApplyAsync(result -> { // 处理异步操作结果 return result + 3; }); // 获取异步操作的结果 Integer result = future.get(); ``` 在上面的代码中,我们首先使用CompletableFuture的supplyAsync方法,将异步操作放在一个CompletableFuture对象中。然后通过thenApplyAsync方法,将两个处理异步操作结果的函数串联在一起,形成一个异步操作流程。最后通过get方法,获取异步操作的结果。 除了thenApplyAsync方法,CompletableFuture还提供了许多其他的方法,比如thenAcceptAsync、thenRunAsync、thenComposeAsync等,可以根据具体需求来选择使用。 总之,CompletableFuture是一个非常强大的异步编程工具类,可以让你写出更加简洁、易读、易维护的异步代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值