通常线程池不仅仅只是这么简单的管理线程,从这个角度出发,线程池可以用来集中管理一系列任务

invokeAny / All

invokeAny 当其中一个线程结束了,别的也就都停止了。可以用在搜索答案

invokeAll 一定要全部线程都结束了才停止,并且返回这些线程的返回值

List<Callable<T>> tasks = . . .;
List<Future<T>> results = executor.invokeAll(tasks);
 
for (Future<T> result : results) 
	processFurther(result.get());  // 顺序遍历,不好
/* ------------------ */
var service = new ExecutorCompletionService<T>(executor); 
for (Callable<T> task : tasks) 
	service.submit(task);
for (int i = 0; i < tasks.size(); i++)
	processFurther(service.take().get());

前面顺序遍历不好的地方在于,他是顺序的,而线程不一定按原本顺序执行完,如果前面没好,后面好了,这样前面就要等后面,浪费时间

所以考虑使用 ExecutorCompletionService 包一下原来的线程池,对新的线程池提交任务。并且注意到这个新的线程池并没有类似于 invokeAll 这样的操作,只能用循环来提交

实际上,ExecutorCompletionService 内部维护了一个 blocking queue,你看有个 take 方法吧

对比一下

用线程池的方法安排,改变了写任务的思路

对比原来的写法,会有一个循环控制线程数量,每个循环就会新建一个线程,同时每个线程里头会有一个循环,不断的执行任务。说实话,这样的写法有些“面向过程”了

而线程池的想法则是有些“面向对象”,我只需要写一系列任务,这些任务可以重复,可以很多很多,不需要任何循环,循环的事情是线程池做的

线程池不会因为任务多,线程也多,他会按照既定的方式安排线程的数量,多个任务就如此巧妙的分配给了不同的线程