如何在ScheduledExecutorService执行scheduleWithFixedDelay监控任务时暂停主线程?
这个问题本质是线程同步的典型场景——你需要让主线程等待监控任务的某个触发条件(比如属性变化被检测并处理完成),或者协调两者的执行节奏。下面给你几个实用的方案,按需选择:
方案一:用CountDownLatch等待第一次属性变化
如果你的需求是主线程暂停到第一次属性变化处理完成后再继续,CountDownLatch是最直接的选择。它就像一个倒计时门闩,监控任务完成触发条件后“开门”,主线程就能继续走了。
代码示例
先修改你的MyClass2,加入CountDownLatch的逻辑:
import java.util.Objects; import java.util.concurrent.CountDownLatch; public class MyClass2 { private final CountDownLatch changeTrigger; private String monitoredValue; private String lastRecordedValue; // 构造时传入CountDownLatch public MyClass2(CountDownLatch changeTrigger) { this.changeTrigger = changeTrigger; this.lastRecordedValue = this.monitoredValue; } public void scanValueChanged() { if (!Objects.equals(monitoredValue, lastRecordedValue)) { // 执行你的处理步骤 System.out.println("属性值已变化,开始执行处理逻辑"); lastRecordedValue = monitoredValue; // 通知主线程可以继续 changeTrigger.countDown(); } } // 给属性加setter(假设你之前有) public void setMonitoredValue(String monitoredValue) { this.monitoredValue = monitoredValue; } }
然后主线程里这样用:
import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) throws InterruptedException { // 初始化门闩,计数为1(等待一次触发) CountDownLatch changeLatch = new CountDownLatch(1); MyClass2 myClass2 = new MyClass2(changeLatch); ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); scheduledExecutorService.scheduleWithFixedDelay( myClass2::scanValueChanged, 1, 45, TimeUnit.SECONDS ); // 主线程暂停,直到门闩计数归0 changeLatch.await(); System.out.println("主线程恢复执行后续逻辑"); // 不需要监控时记得关闭线程池,避免资源泄漏 scheduledExecutorService.shutdown(); } }
适用场景
适合只需要等待第一次属性变化的场景,如果需要多次等待,CountDownLatch是一次性的,你可以每次触发后重新创建新的实例。
方案二:用ScheduledFuture循环等待属性变化
如果需要持续监控直到属性变化,你可以把监控任务包装成Callable,通过ScheduledFuture的get()方法让主线程阻塞等待每次任务的执行结果,直到检测到变化。
代码示例
修改MyClass2让它返回是否检测到变化:
import java.util.Objects; public class MyClass2 { private String monitoredValue; private String lastRecordedValue; public boolean scanValueChanged() { if (!Objects.equals(monitoredValue, lastRecordedValue)) { System.out.println("属性值变化,执行处理逻辑"); lastRecordedValue = monitoredValue; return true; } return false; } public void setMonitoredValue(String monitoredValue) { this.monitoredValue = monitoredValue; } }
主线程代码:
import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class Main { public static void main(String[] args) throws InterruptedException, ExecutionException { MyClass2 myClass2 = new MyClass2(); ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); // 定义返回布尔值的监控任务 var monitorTask = (Runnable & java.util.concurrent.Callable<Boolean>) () -> { boolean isChanged = myClass2.scanValueChanged(); return isChanged; }; // 提交定时任务,获取Future对象 ScheduledFuture<Boolean> future = scheduledExecutorService.scheduleAtFixedRate( monitorTask, 1, 45, TimeUnit.SECONDS ); // 循环等待,直到检测到属性变化 while (!future.get()) { // 每次get()会阻塞主线程,直到当前周期的任务执行完成 } System.out.println("主线程恢复执行"); scheduledExecutorService.shutdown(); } }
注意事项
future.get()会等待当前周期的任务完成,如果你的处理逻辑执行时间超过45秒,下一次任务会延迟执行,这点要提前考虑。
方案三:用观察者模式替代定时轮询(更推荐)
其实定时轮询并不是监控属性变化最优雅的方式——观察者模式可以实现实时触发,属性一变化就立刻执行处理逻辑,从根源上避免线程同步的麻烦。
代码示例
先定义一个观察者接口:
public interface PropertyChangeListener { void onPropertyChanged(String oldValue, String newValue); }
修改MyClass2,在属性变化时主动通知观察者:
import java.util.ArrayList; import java.util.List; import java.util.Objects; public class MyClass2 { private String monitoredValue; private final List<PropertyChangeListener> listeners = new ArrayList<>(); // 添加观察者 public void addChangeListener(PropertyChangeListener listener) { listeners.add(listener); } // 修改属性时触发通知 public void setMonitoredValue(String monitoredValue) { String oldValue = this.monitoredValue; this.monitoredValue = monitoredValue; if (!Objects.equals(oldValue, monitoredValue)) { // 通知所有注册的观察者 for (PropertyChangeListener listener : listeners) { listener.onPropertyChanged(oldValue, monitoredValue); } } } }
主线程用CountDownLatch等待通知:
import java.util.concurrent.CountDownLatch; public class Main { public static void main(String[] args) throws InterruptedException { CountDownLatch changeLatch = new CountDownLatch(1); MyClass2 myClass2 = new MyClass2(); // 注册观察者,处理属性变化逻辑 myClass2.addChangeListener((oldVal, newVal) -> { System.out.printf("属性从%s变为%s,执行处理步骤%n", oldVal, newVal); changeLatch.countDown(); }); // 主线程暂停,直到属性变化被触发 changeLatch.await(); System.out.println("主线程继续执行"); } }
为什么推荐这个方案?
定时轮询会浪费CPU资源(哪怕间隔45秒),而且无法做到实时响应。观察者模式是事件驱动的,属性变化时立刻触发,效率更高,线程同步逻辑也更清晰。除非你的属性不是通过setter修改的(比如第三方库的内部属性),否则优先选这个方案。
内容的提问来源于stack exchange,提问作者Mani Deep

