工作中经常使用到线程池,那如何保证多个线程都执行完后回到主线程呢?其中某个线程异常该如何处理呢?
一、开启多线程
在Java中,我们通常使用Thread
类或者Runnable
接口来创建线程。但是,直接使用Thread
类或者Runnable
接口的线程在完成任务后无法直接返回结果到主线程。这是因为线程一旦启动,其生命周期就不再受主线程控制,主线程无法直接获取子线程的返回值。
一种常见的解决办法是使用Callable
接口和Future
任务。Callable
接口是一个泛型接口,它的call
方法可以返回一个结果,并且可能抛出异常。ExecutorService
可以提交一个Callable
任务并返回一个Future
对象,通过这个Future
对象我们可以取消计算,检查是否完成,并且获取计算结果。
以下是一个简单的例子:
public static void testThread() throws ExecutionException, InterruptedException {
long l = System.currentTimeMillis();
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 创建两个 Callable 任务
Callable<String> task1 = new Callable<String>() {
@Override
public String call() throws Exception {
// 进行一些耗时的计算
Thread.sleep(6000);
return "Result of Task 1";
}
};
Callable<String> task2 = new Callable<String>() {
@Override
public String call() throws Exception {
// 进行一些耗时的计算
Thread.sleep(1000);
return "Result of Task 2";
}
};
// ## 方案一
// 提交任务并获取 Future 对象
Future<String> future1 = executor.submit(task1);
Future<String> future2 = executor.submit(task2);
// 在主线程中进行其他工作...
// 获取任务结果
String result1 = future1.get();
String result2 = future2.get();
// 关闭线程池
executor.shutdown();
// 输出结果
System.out.println(result1);
System.out.println(result2);
// ## 方案二
/*executor.submit(task1);
executor.submit(task2);
// 关闭线程池
executor.shutdown();
try {
// 设置超时时间
if (!executor.awaitTermination(2000, TimeUnit.MILLISECONDS)) {
System.out.println("Tasks took too long to finish.");
}
} catch (InterruptedException e) {
System.err.println("Interrupted while waiting for tasks to finish.");
}*/
System.out.println("耗时:" + (System.currentTimeMillis() - l) + "ms");
}
在这个例子中,我们创建了两个Callable
任务并提交给线程池执行。然后我们在主线程中进行其他工作,当需要的时候,我们通过Future
对象获取任务的结果。注意,future1.get()
和future2.get()
会阻塞直到对应的任务完成。
最后,记得关闭线程池。这将通知线程池停止接收新的任务,并且等待已经提交的任务完成。
二、ExecutorService类中的API有哪些
ExecutorService
接口是java.util.concurrent
包的一部分,它提供了一组方法来管理线程池。以下是一些主要的API方法:
submit(Runnable task):提交一个Runnable任务到线程池,返回一个Future对象,可以用来取消任务或获取任务结果。
<T> Future<T> submit(Callable<T> task):提交一个Callable任务到线程池,返回一个Future对象,可以用来取消任务或获取任务结果。
ExecutorService execute(Runnable command):执行一个Runnable任务,但不返回Future对象,通常用于无返回值的任务。
List<Future<?>> invokeAll(Collection<? extends Callable<V>> tasks):提交一组Callable任务,并等待所有任务完成,返回一个包含所有Future对象的列表。
V invokeAny(Collection<? extends Callable<V>> tasks):提交一组Callable任务,并等待任何一个任务完成,返回该任务的结果。
shutdown():平滑地关闭线程池,等待所有已提交的任务完成,不再接受新的任务。
shutdownNow():立即停止所有正在执行的任务,并尝试停止所有未开始的任务,返回一个包含未执行任务的列表。
isShutdown():检查线程池是否已经关闭。
isTerminated():检查线程池是否已经终止,即所有任务都已经完成。
awaitTermination(long timeout, TimeUnit unit):等待线程池终止,如果在指定时间内没有终止,则返回false。
三、此是多个线程是同步运行吗
在上述示例中,两个任务是并发运行的,而不是同步运行。当你调用executor.submit(task1)
和executor.submit(task2)
时,这两个任务会被提交到线程池并且在线程池的可用线程上并发执行。
Callable
任务本身是异步执行的,这意味着任务的执行不会阻塞主线程。然而,当你调用Future.get()
方法时,它会阻塞当前线程(这里是主线程),直到对应的Callable
任务完成并且结果变得可用。因此,尽管任务是并发执行的,但获取结果的方式是同步的。
如果你希望等待所有任务完成,你可以使用ExecutorService.awaitTermination(long timeout, TimeUnit unit)
方法。这个方法会阻塞,直到所有的任务都完成或者超时为止。例如上面注释掉的代码;主线程会等待最多2000毫秒,看所有提交给executor的任务是否能在这段时间内完成。如果任务没有在指定时间内完成,awaitTermination
方法会返回false
。如果线程在等待过程中被中断,InterruptedException
会被抛出。
四、使用spring注入的方式实现:Executor
@Qualifier("asyncTaskExecutor")
private final Executor asyncTaskExecutor;
public void test(){
CompletableFuture<Object> featuredCF = CompletableFuture
.supplyAsync(() -> "Task 3", asyncTaskExecutor);
Object o = featuredCF.get();
System.out.println(o);
}