Java--多任务并发:如何判断线程池中的任务都已经执行完毕 刺骨的言语ヽ痛彻心扉 2022-12-31 03:23 87阅读 0赞 ### 前言: ### 多线程并发,我们往往采用线程池来管理并发的线程。但是,我们往往有这样的需要:要求在线程池中的任务都完成后才能执行后续的任务,或者需要任务都完成后释放资源或向数据库写入状态。这些都需要我们判断线程池的任务是否都已经完成。 判断线程池中的任务是否全部完成,方式有不少,这里我来整理一下。 一、使用线程池的原生函数isTerminated(); 优点:操作简便; 缺点:需要主线程阻塞; executor提供一个原生函数isTerminated()来判断线程池中的任务是否全部完成。全部完成返回true,否则返回false。 栗子: package my.thread.test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class AnyTask implements Runnable{ public void run() { try { Thread.sleep(3000); System.out.println("普通任务执行执行完毕!"); }catch(Exception e) { e.printStackTrace(); } } } class EndTask { public void taskRun() { try { System.out.println("开始执行最终任务"); }catch(Exception e) { e.printStackTrace(); } } } public class TeminatedTest { public static void main(String[] args) { try { ExecutorService exector = Executors.newFixedThreadPool(7); for(int i =0;i<10;i++) { AnyTask anyTask = new AnyTask(); exector.execute(anyTask); } exector.shutdown(); while(true) { if(exector.isTerminated()) { EndTask endTask = new EndTask(); endTask.taskRun(); break; }else { Thread.sleep(3000); } } }catch(Exception e) { e.printStackTrace(); } } } 操作很方便,但要求主线程阻塞,这样对接口化编程很不友好。 ### 二、使用CountDownLatch: ### 优点:操作相对简便,可以把等待线程池中任务完成后的后续工作做成任务,同样放到线程池中运行,简单来说,就是可以控制线程池中任务执行的顺序。 缺点: 需要提前知道任务的数量。 原理:其工作原理是赋给CountDownLatch一个计数值,普通的任务执行完毕后,调用countDown()执行计数值减一。最后执行的任务在调用方法的开始调用await()方法,这样整个任务会阻塞,直到这个计数值(Count)为零,才会继续执行。 栗子: package my.thread.test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class AnyTask implements Runnable{ private CountDownLatch endTaskLatch; public AnyTask(CountDownLatch latch) { this.endTaskLatch = latch; } public void run() { try { Thread.sleep(10000); System.out.println("普通任务执行执行完毕!"); endTaskLatch.countDown(); System.out.println(endTaskLatch.getCount()); }catch(Exception e) { e.printStackTrace(); } } } class EndTask implements Runnable{ private CountDownLatch endTaskLatch; EndTask(CountDownLatch latch) { this.endTaskLatch =latch; } public void run() { try { endTaskLatch.await(); System.out.println("开始执行最终任务"); System.out.println(endTaskLatch.getCount()); }catch(Exception e) { e.printStackTrace(); } } } public class CountDownLatchTest { public static void main(String[] args) { ExecutorService exector = Executors.newFixedThreadPool(7); CountDownLatch TaskLatch = new CountDownLatch(10); EndTask endTask = new EndTask(TaskLatch); exector.execute(endTask); for(int i=0;i<10;i++) { exector.execute(new AnyTask(TaskLatch)); } System.out.println("主线程已经执行完了"); } } 根据打印结果我们可以清楚的看到你,endTask等待所有的AnyTask执行完毕才继续执行,否则会一直阻塞下去。 当然,我们也可以使用await()一直阻塞。在主线程调用即可。 三、使用重入锁,维持一个公共计数: 第三种方式,实行起来基本上和CountDownLatch 的原理差不多。 所有的普通任务维持一个static的计数器,当任务完成时计数器加一(这里要用锁),当计数器的值等于任务数时,这时所有的任务已经执行完毕了,这时endTask就会自动执行。 所有的任务,包括endTask都可以放到线程池中,当普通任务未执行完毕时,如果endTask开始执行,也会一直阻塞等待,直到公共计数等于等于任务数。 优点:灵活、可控 缺点:需要预知运算的任务数、操作相对有点繁琐 栗子: package my.thread.test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class AnyTask implements Runnable{ public static int taskNum = 0; private Lock lock = new ReentrantLock(); public void run() { try { Thread.sleep(3000); System.out.println("普通任务执行执行完毕!"); lock.lock(); AnyTask.taskNum++; lock.unlock(); }catch(Exception e) { e.printStackTrace(); } } } class EndTask implements Runnable{ private int tnum = 0; public EndTask(int tnum) { this.tnum = tnum; } public void run() { try { while(true) { if(tnum==AnyTask.taskNum) { System.out.println("开始执行最终任务"); break; }else { System.out.println("线程池的任务没有完成,等待。。。"); Thread.sleep(3000); } } }catch(Exception e) { e.printStackTrace(); } } } public class JishuTest { public static void main(String[] args) { ExecutorService exector = Executors.newFixedThreadPool(7); int taskNum = 10; for(int i=0;i<taskNum;i++) { AnyTask anyTask = new AnyTask(); exector.execute(anyTask); } EndTask endTask = new EndTask(taskNum); exector.execute(endTask); exector.shutdown(); } } 四、submit向线程池提交任务,Future判断任务执行状态: 使用submit向线程池提交任务与execute提交不同,submit会有Future类型的返回值。通过返回返回值可以用于判断线程任务的执行状态。 举例: package com.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Test { public static void main(String[] args) { ExecutorService exector = Executors.newFixedThreadPool(7); Future<?> future = exector.submit(new Runnable(){ @Override public void run() { int i = 5; while(i>=0) { try { System.out.println("正在执行线程!i="+i); Thread.sleep(10000); i--; }catch(Exception e) { e.printStackTrace(); } } } }); while(true) { if(future.isDone()) { break; } System.out.println("主线程等待..."); try { Thread.sleep(5000); }catch(Exception e) { e.printStackTrace(); } } System.out.println("主线程已经执行完了"); } } 打印日志: 主线程等待... 正在执行线程!i=5 主线程等待... 主线程等待... 正在执行线程!i=4 主线程等待... 正在执行线程!i=3 主线程等待... 主线程等待... 正在执行线程!i=2 主线程等待... 主线程等待... 正在执行线程!i=1 主线程等待... 主线程等待... 正在执行线程!i=0 主线程等待... 主线程等待... 主线程已经执行完了 ### 不同的应用场景应采用适合的方式,以上、谢谢 ! ###
还没有评论,来说两句吧...