我们在直接使用Thread线程类的时候可以用join方法解决主线程等待子线程执行完毕的需求,但是在实际开发中我们用的大多是线程池,没有join方法给我们调用。这种情况JAVA提供了两种解决方法。
第一种:CountDownLatch
CountDownLatch使用比较直白,它直观的伴随着子线程的结束而将自身的任务数递减,到0时主线程继续,使用的时候不要倒错包。
java.util.concurrent.CountDownLatch
使用它,你只需要在向线程池提交任务前,new一个CountDownLatch,并指定任务总数大小就可以,比如提交5个任务,你就new一个5个count的
CountDownLatch countDownLatch = new CountDownLatch(5);
随后在提交的任务中,末尾该任务逻辑执行结束的地方调用,减一方法,它的作用就是告诉CountDownLatch已经完成某个任务并把任务数递减
countDownLatch.countDown();
最后在主线程的末尾使用等待方法,它会等待任务数为0的时候解除线程阻塞
countDownLatch.await();
同时它也提供了获取当前任务数的方法
countDownLatch.getCount()
第二种:CyclicBarrier
CyclicBarrier是当所有子线程执行到某一步的时候停止,并等待其他子线程,当所有子线程执行到特定地方后,所有线程开始继续。
public class CycleBarrierTest {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
// 当计数器为0时,立即执行
@Override
public void run() {
System.out.println("汇总线程:" + Thread.currentThread().getName() + " 任务合并。");
}
});
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 将线程A添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("线程A:" + Thread.currentThread().getName() + "执行任务。");
System.out.println("线程A:到达屏障点");
cyclicBarrier.await();
System.out.println("线程A:退出屏障点");
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 将线程B添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
System.out.println("线程B:" + Thread.currentThread().getName() + "执行任务。");
System.out.println("线程B:到达屏障点");
cyclicBarrier.await();
System.out.println("线程B:退出屏障点");
} catch (Exception e) {
e.printStackTrace();
}
}
});
// 关闭线程池
executorService.shutdown();
}
}
线程A:pool-1-thread-1执行任务。
线程A:到达屏障点
线程B:pool-1-thread-2执行任务。
线程B:到达屏障点
汇总线程:pool-1-thread-2 任务合并。
线程B:退出屏障点
线程A:退出屏障点
一般不用CyclicBarrier,它的缺点很明显,当所有的线程达到某一步的时候,阻塞就失效了,后面的代码并不会在主线程前结束
除了上面两种,其实在使用的时候,还有一种方法,只不过很少会用到啊,因为它比较鸡肋,上面两种方法特点是直接干涉了线程池内部任务运行时的流程,而我现在说的这种方法是无法直接干涉子线程,常是来观察子线程运行的状态,在运行的流程上和,上面CyclicBarrier有点像,很少用,通常都是用在对任务本身的状态做相关操作时才会用。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class ThreadPoolExample {
private static ExecutorService threadPool = Executors.newFixedThreadPool(5);
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<Future<String>> futures = new ArrayList<>();
// 提交任务到线程池
for (int i = 0; i < 10; i++) {
Callable<String> task = new MyTask();
Future<String> future = threadPool.submit(task);
futures.add(future);
}
// 等待任务完成,可以根据需要设置超时时间
for (Future<String> future : futures) {
try {
String result = future.get(); // 等待任务完成并获取结果
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException e) {
// 处理任务中断或执行异常的情况
e.printStackTrace();
}
}
// 关闭线程池
threadPool.shutdown();
}
}
上述示例中,使用Future对象来获取任务的执行结果,并在任务完成后执行相应的代码。可以在获取结果的时候进行判断,如果任务被中止或停止,可以在相应的catch块中添加自定义的代码来处理。例如,可以记录日志、执行清理操作等。
需要注意的是,Future对象的get()方法是阻塞的,会等待任务执行完成并获取结果。如果想设置超时时间,可以使用get(long timeout, TimeUnit unit)方法,并在指定的时间内获取结果。如果任务超时未完成,会抛出TimeoutException异常。
try {
String result = future.get(1, TimeUnit.SECONDS); // 设置超时时间为1秒
System.out.println("Task result: " + result);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// 处理任务中断、执行异常或超时的情况
e.printStackTrace();
}