预备知识
- 理论来源:《Java实战(第2版)》16.3.4 使用定制的执行器
调整线程池的大小,《Java 并发编程实战》中的公式
线程数 = Cpu 核心数 * 期望的 CPU 利用率(0和1之间) * (1 + 等待时间/计算时间)
- CPU 核心数可以通过 Runtime.getRuntime.availableProcessors() 得到
- 避免过载,最好设置线程数上限
并行:使用流还是 CompletableFuture ?
- 流
- 计算密集型
- 没有 I/O
- 因为实现简单,同时效率也可能是最高的
- CompletableFuture
- 涉及等待 I/O 操作(包括网络连接)
- 灵活性好(可以根据实际情况设置线程数)
- 不使用并行流的原因之一:处理流的流水线中如果发生 I/O 等待,流的延迟特性会让我们很难判断到底什么时候触发了等待
核心代码
private final Executor executor =
Executors.newFixedThreadPool(Math.min(shops.size(), 100), r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
});
CompletableFuture.supplyAsync(() -> shop.getName() + " price is" + shop.getPrice(product), executor)
完整代码
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* Java 最强并行
* @author aoe
* @date 2021/2/20
*/
public class BestParallel {
private final List<Shop> shops = getShops(100);
private final Executor executor =
Executors.newFixedThreadPool(Math.min(shops.size(), 100), r -> {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
});
public static void main(String[] args) {
BestParallel test = new BestParallel();
long start = System.nanoTime();
test.findPrices("car");
long duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("普通版 done in " + duration + " msecs");
// 15115
start = System.nanoTime();
test.findPricesBest("car");
duration = (System.nanoTime() - start) / 1_000_000;
System.out.println("最强版 done in " + duration + " msecs");
// 1020
}
public List<String> findPrices(String product) {
List<CompletableFuture<String>> priceFutures =
shops.stream()
// 使用 CompletableFuture 以异步方式及时每种商品的价格
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is" + shop.getPrice(product))
)
.collect(Collectors.toList());
return priceFutures.stream()
// 等待所有异步操作结束
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
public List<String> findPricesBest(String product) {
List<CompletableFuture<String>> priceFutures =
shops.stream()
// 使用 CompletableFuture 以异步方式及时每种商品的价格
.map(shop -> CompletableFuture.supplyAsync(() -> shop.getName() + " price is" + shop.getPrice(product), executor)
)
.collect(Collectors.toList());
return priceFutures.stream()
// 等待所有异步操作结束
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
private List<Shop> getShops(int count) {
List<Shop> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(new Shop("Shop" + i));
}
return list;
}
}
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 String getPrice(String product) {
double price = calculatePrice(product);
Discount.Code code = Discount.Code.values()[random.nextInt(Discount.Code.values().length)];
return name + ":" + price + ":" + code;
}
public double calculatePrice(String product) {
Util.delay();
return Util.format(random.nextDouble() * product.charAt(0) + product.charAt(1));
}
public String getName() {
return name;
}
}
class Discount {
public enum Code {
NONE(0), SILVER(5), GOLD(10), PLATINUM(15), DIAMOND(20);
private final int percentage;
Code(int percentage) {
this.percentage = percentage;
}
}
public static String applyDiscount(Quote quote) {
return quote.getShopName() + " price is " + Discount.apply(quote.getPrice(), quote.getDiscountCode());
}
private static double apply(double price, Code code) {
Util.delay();
return Util.format(price * (100 - code.percentage) / 100);
}
}
class Util {
private static final Random RANDOM = new Random(0);
private static final DecimalFormat formatter = new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US));
public static void delay() {
int delay = 1000;
//int delay = 500 + RANDOM.nextInt(2000);
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static double format(double number) {
synchronized (formatter) {
return new Double(formatter.format(number));
}
}
public static <T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {
/*
CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream()
.map(future -> future.join())
.collect(Collectors.<T>toList())
);
*/
return CompletableFuture.supplyAsync(() -> futures.stream()
.map(future -> future.join())
.collect(Collectors.<T>toList()));
}
}
class Quote {
private final String shopName;
private final double price;
private final Discount.Code discountCode;
public Quote(String shopName, double price, Discount.Code discountCode) {
this.shopName = shopName;
this.price = price;
this.discountCode = discountCode;
}
public static Quote parse(String s) {
String[] split = s.split(":");
String shopName = split[0];
double price = Double.parseDouble(split[1]);
Discount.Code discountCode = Discount.Code.valueOf(split[2]);
return new Quote(shopName, price, discountCode);
}
public String getShopName() {
return shopName;
}
public double getPrice() {
return price;
}
public Discount.Code getDiscountCode() {
return discountCode;
}
}