活动地址:CSDN21天学习挑战赛
7.1 线程池
高并发编程要解决的核心问题:由于创建单独的线程是需要消耗一定的系统资源的,因此线程的数量在一定时间是有限的。
使用一个单独的线程执行任务:
-
需要向系统申请一定的空间资源
-
同时,这个过程(创建和销毁)也需要耗费一定时间
从JDK 5开始,提供了线程池(Thread Pool)来解决这个问题。线程池,就是存放多个线程的一个容器,这里面的线程可以反复使用。线程池的工作模式具有以下特点:
-
当一个线程执行完任务后,会重新放回到线程池(而不是销毁),这样它可以继续在执行后续的任务中得到重用。
-
线程池中的线程是相对固定和有限的,因此不会因为依据太多的任务创建太多的线程而导致Out Of Memory,OOM。
-
同时,线程池还会管理如何将任务分配给执行的线程来执行:当所有线程都处于繁忙时,那么任务将会被放在一个队列(Queue)中等待,只要有一个线程空闲了,就会从队列中取出一个任务分配给这个空闲的线程来执行。
有了线程池,我们就不需要直接来创建线程,而只需要向线程池提交任务,有线程池来进行管理任务的具体分配。
7.2 Executors
public class Main {
public static void main(String[] args) {
var service = Executors.newFixedThreadPool(2);
try {
for (int i = 0; i < 5; i++) {
service.submit(() -> System.out.println(Thread.currentThread().getName()));
}
} finally {
service.shutdown();
}
}
}
7.3 Callable & Future
我们还可以使用Callable接口来提交有返回值的任务,得到Feature作为结果
public class LongTask {
public static void simulate(int ms){
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
var service = Executors.newFixedThreadPool(2);
try {
var future = service.submit(() -> {
LongTask.simulate(3_000);
return 1;
});
System.out.println("...");
var result = future.get();
System.out.println(result);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}
}
在提交任务之后,程序会立即返回Future对象作为返回值,但是任务的返回结果并没有得到,一直等到任务执行完毕,我们才能获取到任务执行的结果。(submit方法并不会阻塞程序,但是get方法会阻塞直至任务执行完毕得到返回值)。
7.4 异步编程
在上述案例中,由于最终获取到任务执行结果是需要消耗时间的,因此在此期间,get方法会一直处于阻塞状态。在主线程上,阻塞状态是非常不理想的,因为,它会降低整个程序的响应速度,比如主线程还有其它的事情要做:接收用户的点击事件,同步更新页面上其它数据,等等。这种场景,程序就处于同步状态(synchronous)。为了提高整个应用程序的响应速度和用户体验,我们需要继续对上述代码进行异步化处理(asynchronous)。
7.4.1 CompletableFuture
public class Main {
public static void main(String[] args) {
// Runnable task = () -> System.out.println("a");
// var future = CompletableFuture.runAsync(task);
Supplier<Integer> supplier = () -> 1;
var future = CompletableFuture.supplyAsync(supplier);
try {
var result = future.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
7.4.2 Asynchronous API
常规异步案例:
public class MailService {
public void send() {
System.out.println("发送邮件ing...");
LongTask.simulate(3_000);
System.out.println("发送完毕...");
}
public CompletableFuture<Void> sendAsync() {
return CompletableFuture.runAsync(this::send);
}
}
public class Main {
public static void main(String[] args) {
var service = new MailService();
service.sendAsync();
System.out.println("...");
LongTask.simulate(5_000);
}
}
7.4.3 异步链
public class Main {
public static void main(String[] args) {
var future = CompletableFuture.supplyAsync(() -> {
System.out.println("异步逻辑");
return 1;
});
future.thenRunAsync(() -> {
System.out.println(Thread.currentThread().getName());
System.out.println("后续逻辑.");
});
}
}
异步任务有返回值,在后续任务中获取异步任务处理结果:
public class Main {
public static void main(String[] args) {
var future = CompletableFuture.supplyAsync(() -> {
System.out.println("异步逻辑");
return 1;
});
future.thenRunAsync(() -> {
System.out.println(Thread.currentThread().getName());
System.out.println("后续逻辑.");
});
}
}
7.4.4 异常处理
public class Main {
public static void main(String[] args) {
var future = CompletableFuture.supplyAsync(() -> {
System.out.println("异步逻辑");
throw new IllegalStateException();
});
}
}
在上述案例中,如果异步任务中如果发生异常的话,那么在主线程中是获取不到的(因为异常是在另一个新的线程中发生的),如果要获取的异常信息并进行相应处理的话,需要如下编码:
public class Main {
public static void main(String[] args) {
var future = CompletableFuture.supplyAsync(() -> {
System.out.println("异步逻辑");
throw new IllegalStateException();
});
try {
// var result = future.get();
var result = future.exceptionally(ex -> "默认值").get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
如果直接在future上调用get(),我们将会得到异常;如果在此之前,调用exceptionally()将会得到一个新的CompletableFuture对象,我们可以在其中返回一个当异常发生时的默认值作为处理手段。
7.4.5 转换返回结果
对上一次的异步任务的返回结果进行数据类型的转换:
public class Main {
public static void main(String[] args) {
var future = CompletableFuture.supplyAsync(() -> 26);
future.thenApply(c -> Float.toString(c * 1.8f + 32))
.thenAccept(System.out::println);
}
}
7.4.6 Compose组合
public class Main {
public static CompletableFuture<String> findEmailByIdAsync(int id) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("根据id:" + id + "查询DB");
return "a@b.com";
});
}
public static CompletableFuture<String> findListByEmailAsync(String email) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("根据email:" + email + "查询列表");
return "播放列表";
});
}
public static void main(String[] args) {
findEmailByIdAsync(1)
.thenCompose(Main::findListByEmailAsync)
.thenAccept(System.out::println);
LongTask.simulate(1000);
}
}
7.4.7 Combine结果组合
public class Main {
public static void main(String[] args) {
var task1 = CompletableFuture.supplyAsync(() -> {
System.out.println("获取结算金额(美元)");
return 20;
});
var task2 = CompletableFuture.supplyAsync(() -> {
System.out.println("获取结算汇率");
return 0.9;
});
task1.thenCombine(task2, (price, rate) -> price * rate)
.thenAcceptAsync(System.out::println);
}
}
7.4.8 多任务等待
等待所有任务:
public class Main {
public static void main(String[] args) {
var task1 = CompletableFuture.supplyAsync(() -> 1);
var task2 = CompletableFuture.supplyAsync(() -> 2);
var task3 = CompletableFuture.supplyAsync(() -> 3);
var all = CompletableFuture.allOf(task1, task2, task3);
all.thenRun(() -> {
System.out.println("后续逻辑");
try {
var res1 = task1.get();
System.out.println(res1);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
等待首个完成的任务(竞争问题):
public class Main {
public static void main(String[] args) {
var task1 = CompletableFuture.supplyAsync(() -> {
LongTask.simulate(1000);
return 1;
});
var task2 = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture.anyOf(task1, task2)
.thenAccept(System.out::println);
}
}
7.4.9 超时任务
public class Main {
public static void main(String[] args) {
var future = CompletableFuture.supplyAsync(() -> {
LongTask.simulate(3000);
return 1;
});
try {
var result = future.completeOnTimeout(0, 1, TimeUnit.SECONDS)
.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
7.5 Project 询价
向各个航空公司售票代理询价,一旦有询价结果,便输出显示
public class Quote {
private String name;
private int price;
public Quote(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Quote{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
public class FlightService {
private final Random random = new Random();
public Stream<CompletableFuture<Quote>> query() {
var list = List.of("a", "b", "c");
return list.stream()
.map(this::query);
}
public CompletableFuture<Quote> query(String name) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("询价:" + name);
var delay = random.nextInt(2_000) + 1_000;
LongTask.simulate(delay);
var price = random.nextInt(10) + 100;
return new Quote(name, price);
});
}
}
public class Main {
public static void main(String[] args) {
var start = LocalTime.now();
var service = new FlightService();
var arr = service
.query()
.map(future -> future.thenAccept(System.out::println))
.toArray(CompletableFuture[]::new);
var all = CompletableFuture.allOf(arr);
all.thenRun(() -> {
var end = LocalTime.now();
var duration = Duration.between(start, end);
System.out.println("总耗时:" + duration.toMillis());
});
LongTask.simulate(3_000);
}
}