等待Java线程结束同时执行其他任务的最优方案是什么?
嘿,我完全懂你的痛点——既要等线程结束,又得同时处理其他任务,join()会阻塞主线程肯定不行,isAlive()又有各种坑还不被推荐。这里给你几个简单好用的替代方案,都是业界常用的:
方案1:用CountDownLatch(最直观的同步工具)
CountDownLatch就是专门用来等待一个或多个线程完成的工具,你可以用它来替代stillRunning()的判断逻辑,而且比轮询isAlive()靠谱多了。
修改你的代码示例:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; public class MyService implements Runnable { private final CountDownLatch latch; // 构造时传入latch public MyService(CountDownLatch latch) { this.latch = latch; } public void run() { try { for (int j = 0; j < 5; j++){ TimeUnit.SECONDS.sleep(1); System.out.println("Hello from a thread!"); } } catch (InterruptedException e){ // 处理中断(哪怕你不care,也可以重置中断状态) Thread.currentThread().interrupt(); } finally { // 线程结束时,一定要countDown latch.countDown(); } } @RequestMapping(value = "/test") @ResponseBody void test(HttpServletResponse response) { CountDownLatch latch = new CountDownLatch(1); Thread t = new Thread(new MyService(latch)); t.start(); while (latch.getCount() > 0) { // 用getCount()判断线程是否还在运行 System.out.println("Yup, t is still running"); // 这里处理你的重要任务 try { // 可以加个短睡眠,避免空轮询占用CPU TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } System.out.println("Ok, now we know t is done"); } }
这个方案的好处是:CountDownLatch的状态变化是线程安全的,而且你可以通过getCount()明确知道线程是否完成,完全避开isAlive()的坑。
方案2:自定义volatile标志位(最简单的轻量方案)
如果你觉得用同步工具太“重”,可以自己定义一个volatile的布尔变量来标记线程状态。volatile关键字能保证变量的可见性,主线程能及时看到线程结束的状态变化。
示例代码:
import java.util.concurrent.TimeUnit; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; public class MyService implements Runnable { // volatile保证多线程下的可见性 private volatile boolean isRunning = true; public void run() { try { for (int j = 0; j < 5; j++){ TimeUnit.SECONDS.sleep(1); System.out.println("Hello from a thread!"); } } catch (InterruptedException e){ Thread.currentThread().interrupt(); } finally { // 线程结束时设置为false isRunning = false; } } // 对外提供判断方法 public boolean stillRunning() { return isRunning; } @RequestMapping(value = "/test") @ResponseBody void test(HttpServletResponse response) { MyService service = new MyService(); Thread t = new Thread(service); t.start(); while (service.stillRunning()) { System.out.println("Yup, t is still running"); // 处理重要任务 try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } System.out.println("Ok, now we know t is done"); } }
这个方案最接近你想要的stillRunning()方法,代码改动最小,而且完全可控,不用担心isAlive()的那些奇怪问题。
方案3:用ExecutorService + Future(更规范的线程池方式)
如果你平时用线程池来管理线程,那Future的isDone()方法就是专门用来判断任务是否完成的,比isAlive()靠谱得多,而且还能顺便处理线程的返回值(如果需要的话)。
示例代码:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletResponse; public class MyService implements Runnable { public void run() { try { for (int j = 0; j < 5; j++){ TimeUnit.SECONDS.sleep(1); System.out.println("Hello from a thread!"); } } catch (InterruptedException e){ Thread.currentThread().interrupt(); } } @RequestMapping(value = "/test") @ResponseBody void test(HttpServletResponse response) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<?> future = executor.submit(new MyService()); while (!future.isDone()) { // 用future.isDone()判断任务是否完成 System.out.println("Yup, t is still running"); // 处理重要任务 try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } System.out.println("Ok, now we know t is done"); executor.shutdown(); // 别忘了关闭线程池 } }
这个方案的优势是:线程池管理线程更规范,Future的isDone()是专门为任务状态设计的,没有isAlive()的那些边缘情况问题。
为什么不推荐isAlive()?
你提到的文档矛盾其实是边缘情况:比如线程已经调用了start(),但JVM还没真正调度它执行run(),这时候isAlive()可能返回false;另外,线程终止后,isAlive()的状态更新可能存在可见性问题(因为没有同步机制),导致主线程不能及时看到线程结束的状态。而上面的三个方案都能完美避开这些问题。
内容的提问来源于stack exchange,提问作者Stephen

