[学习总结] 对产品实现汇率换算服务(将两个CompletableFuture对象整合起来,无论它们是否存在依赖)

文章介绍了如何利用Java8的CompletableFuture在异步编程中处理商品价格查询和汇率转换,展示了多种方法来结合查询结果,包括thenCombine和thenApply,并分析了不同实现的性能表现。
摘要由CSDN通过智能技术生成

需求

有一家商店提供的价格是以欧元(EUR)计价的,但是你希望以美元的方式提供给你的客户。你可以用异步的方式向商店查询指定商品的价格,同时从远程的汇率服务那里查到欧元和美元之间的汇率。当二者都结束时,再将这两个结果结合起来,用返回的商品价格乘以当时的汇率,得到以美元计价的商品价格。

实现源码

获取价格

import com.practisesvr.utils.DelayUtils;
import lombok.Data;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;

import static com.zhangziwa.practisesvr.utils.DelayUtils.getMoment;

@Data
public class Shop {

    private final String name;
    private final Random random;

    public Shop(String name) {
        this.name = name;
        random = new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));
    }

    public double getPrice(String product) {
        return calculatePrice(product);
    }

    private double calculatePrice(String product) {
        DelayUtils.delay();
        System.out.println(getMoment() + " " + Thread.currentThread().getName() + " 线程执行 calculatePrice");
        // 依据产品的名称,生成一个随机值作为价格
        return random.nextDouble() * product.charAt(0) + product.charAt(1);
    }
}

汇率换算

import com.zhangziwa.practisesvr.utils.DelayUtils;

// 汇率换算服务
public class ExchangeService {

    public enum Money {
        USD(1.0), EUR(1.35387), GBP(1.69715), CAD(.92106), MXN(.07683);

        private final double rate;

        Money(double rate) {
            this.rate = rate;
        }
    }

    public static double getRate(Money source, Money destination) {
        return getRateWithDelay(source, destination);
    }

    private static double getRateWithDelay(Money source, Money destination) {
        DelayUtils.delay();
        return destination.rate / source.rate;
    }
}

实现方案

方案1:thenCombine → future结果map加工

    public List<String> findPricesInUSD(String product) {
        List<CompletableFuture<Double>> priceFutures = new ArrayList<>();
        for (Shop shop : shops) {
            CompletableFuture<Double> futurePriceInUSD = CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor)
                    .thenCombine(CompletableFuture.supplyAsync(() -> ExchangeService.getRate(Money.EUR, Money.USD), executor), (price, rate) -> price * rate);
            priceFutures.add(futurePriceInUSD);
        }

        List<String> prices = priceFutures.stream().map(CompletableFuture::join).map(price -> " price is " + price).collect(Collectors.toList());
        return prices;
    }

方案2:thenCombine+thenApply+list.add

    public List<String> findPricesInUSD2(String product) {
        List<CompletableFuture<String>> priceFutures = new ArrayList<>();
        for (Shop shop : shops) {
            CompletableFuture<String> futurePriceInUSD = CompletableFuture.supplyAsync(() -> shop.getPrice(product), executor)
                            .thenCombine(CompletableFuture.supplyAsync(() -> ExchangeService.getRate(Money.EUR, Money.USD), executor), (price, rate) -> price * rate)
                            .thenApply(price -> shop.getName() + " price is " + price);
            priceFutures.add(futurePriceInUSD);
        }

        List<String> prices = priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
        return prices;
    }

方案3:thenCombine+thenApply+toList(自定义线程池)

private final Executor executor = Executors.newFixedThreadPool(shops.size(), ExecuterThreadFactoryBuilder.build());

    public List<String> findPricesInUSD4(String product) {
        List<CompletableFuture<String>> priceFutures = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product),executor)
                        .thenCombine(CompletableFuture.supplyAsync(() -> ExchangeService.getRate(Money.EUR, Money.USD), executor), (price, rate) -> price * rate)
                        .thenApply(price -> shop.getName() + " price is " + price))
                .collect(Collectors.toList());

        List<String> prices = priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
        return prices;
    }

2024-04-21 09:14:11 thread-2 执行calculatePrice LetsSaveBig
2024-04-21 09:14:11 thread-1 getRate BestPrice
2024-04-21 09:14:11 thread-0 执行calculatePrice BestPrice
2024-04-21 09:14:11 thread-2 getRate LetsSaveBig
2024-04-21 09:14:11 thread-0 执行calculatePrice ShopEasy
2024-04-21 09:14:11 thread-0 getRate ShopEasy
2024-04-21 09:14:12 thread-2 thenCombine打折 LetsSaveBig
2024-04-21 09:14:12 thread-1 thenCombine打折 BestPrice
2024-04-21 09:14:12 thread-0 thenCombine打折 ShopEasy
2024-04-21 09:14:12 thread-1 thenApply BestPrice
2024-04-21 09:14:12 thread-2 thenApply LetsSaveBig
2024-04-21 09:14:12 thread-0 thenApply ShopEasy
2024-04-21 09:14:11 main [BestPrice price is 91.040141702717, LetsSaveBig price is 125.1710573102377, ShopEasy price is 130.0591978746988]

性能比较 total cost time : 1064 ms
combined USD thenCombine+thenApply+toList+supplyAsync cost : 1064 ms, 100.00%

方案4:thenCombine+thenApply+toList(默认线程池)

    public List<String> findPricesInUSD3(String product) {
        List<CompletableFuture<String>> priceFutures = shops.stream()
                .map(shop -> CompletableFuture.supplyAsync(() -> shop.getPrice(product))
                        .thenCombine(CompletableFuture.supplyAsync(() -> ExchangeService.getRate(Money.EUR, Money.USD)), (price, rate) -> price * rate)
                        .thenApply(price -> shop.getName() + " price is " + price))
                .collect(Collectors.toList());

        List<String> prices = priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
        return prices;
    }

2024-04-21 09:16:06 ForkJoinPool.commonPool-worker-3 执行calculatePrice LetsSaveBig
2024-04-21 09:16:06 ForkJoinPool.commonPool-worker-2 getRate BestPrice
2024-04-21 09:16:06 ForkJoinPool.commonPool-worker-4 getRate LetsSaveBig
2024-04-21 09:16:06 ForkJoinPool.commonPool-worker-1 执行calculatePrice BestPrice
2024-04-21 09:16:06 ForkJoinPool.commonPool-worker-6 getRate ShopEasy
2024-04-21 09:16:06 ForkJoinPool.commonPool-worker-5 执行calculatePrice ShopEasy
2024-04-21 09:16:07 ForkJoinPool.commonPool-worker-2 thenCombine打折 BestPrice
2024-04-21 09:16:07 ForkJoinPool.commonPool-worker-6 thenCombine打折 ShopEasy
2024-04-21 09:16:07 ForkJoinPool.commonPool-worker-4 thenCombine打折 LetsSaveBig
2024-04-21 09:16:07 ForkJoinPool.commonPool-worker-6 thenApply ShopEasy
2024-04-21 09:16:07 ForkJoinPool.commonPool-worker-2 thenApply BestPrice
2024-04-21 09:16:07 ForkJoinPool.commonPool-worker-4 thenApply LetsSaveBig
2024-04-21 09:16:06 main [BestPrice price is 91.040141702717, LetsSaveBig price is 125.1710573102377, ShopEasy price is 130.0591978746988]

性能比较 total cost time : 1058 ms
combined USD thenCombine+thenApply+toList+supplyAsync cost : 1058 ms, 100.00%

方案5:Java7写法

// 为了更直观地感受一下使用CompletableFuture在代码可读性上带来的巨大提升,尝试仅使用Java 7中提供的特性实现以下如上实现
public List<String> findPricesInUSDJava7(String product) {
    ExecutorService executor = Executors.newCachedThreadPool();
    
    List<Future<Double>> priceFutures = new ArrayList<>();
    
    for (Shop shop : shops) {
        final Future<Double> futureRate = executor.submit(new Callable<Double>() {
            public Double call() {
                return ExchangeService.getRate(Money.EUR, Money.USD);
            }
        });
        
        Future<Double> futurePriceInUSD = executor.submit(new Callable<Double>() {
            public Double call() {
                try {
                    double priceInEUR = shop.getPrice(product);
                    return priceInEUR * futureRate.get();
                } catch (InterruptedException | ExecutionException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        });
        
        priceFutures.add(futurePriceInUSD);
    }
    
    List<String> prices = new ArrayList<>();
    for (Future<Double> priceFuture : priceFutures) {
        try {
            prices.add(" price is " + priceFuture.get());
        }
        catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
    }
    return prices;
}

2024-04-09 21:59:37 pool-2-thread-10线程 执行calculatePrice
2024-04-09 21:59:37 pool-2-thread-4线程 执行calculatePrice
2024-04-09 21:59:37 pool-2-thread-2线程 执行calculatePrice
2024-04-09 21:59:37 pool-2-thread-8线程 执行calculatePrice
2024-04-09 21:59:37 pool-2-thread-6线程 执行calculatePrice
2024-04-09 21:59:36 main [ price is 91.040141702717,  price is 125.1710573102377,  price is 158.16078708139523,  price is 136.45612204497712,  price is 130.0591978746988]

性能比较 total cost time : 1056 ms
combined USD findPricesInUSDJava7        cost : 1056 ms, 100.00%

我们能看到创建流水线对同步和异步操作进行混合操作有多么简单,随着处理任务和需要合并结果数目的增加,这种声明式程序设计的优势也愈发明显。


测试

import com.zhangziwa.practisesvr.utils.StopWatchUtils;
import org.springframework.util.StopWatch;

import java.util.List;
import java.util.function.Supplier;

import static com.zhangziwa.practisesvr.utils.DelayUtils.getMoment;

public class BestPriceFinderMain {
    private static BestPriceFinder bestPriceFinder = new BestPriceFinder();

    public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch("性能比较");
        execute("combined USD CompletableFuture v1", () -> bestPriceFinder.findPricesInUSD("myPhone27S"), stopWatch);
        execute("combined USD CompletableFuture v2", () -> bestPriceFinder.findPricesInUSD2("myPhone27S"), stopWatch);
        execute("combined USD CompletableFuture v4", () -> bestPriceFinder.findPricesInUSD4("myPhone27S"), stopWatch);
        execute("combined USD CompletableFuture v3", () -> bestPriceFinder.findPricesInUSD3("myPhone27S"), stopWatch);
        execute("combined USD findPricesInUSDJava7", () -> bestPriceFinder.findPricesInUSDJava7("myPhone27S"), stopWatch);
        StopWatchUtils.logStopWatch(stopWatch);
    }


    private static void execute(String msg, Supplier<List<String>> s, StopWatch stopWatch) {
        stopWatch.start(msg);
        System.out.println(getMoment() + " " + Thread.currentThread().getName() + " " + s.get());
        stopWatch.stop();
        System.out.println();
    }
}

性能比较 total cost time : 8123 ms
combined USD thenCombine                 								cost : 2063 ms, 25.40%
combined USD thenCombine+thenApply       								cost : 2010 ms, 24.74%
combined USD thenCombine+thenApply+toList+supplyAsyncExecutor 			cost : 2013 ms, 24.78%
combined USD thenCombine+thenApply+toList+supplyAsync 					cost : 1018 ms, 12.53%
combined USD Java7                       								cost : 1017 ms, 12.52%

参考

《Java8 实战》第11章 CompletableFuture:组合式异步编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值