第一时间收到文章更新
场景说明
假设我们有以下简单代码,旨在每隔 3 秒检查一次标记量 FLAG
,如果 FLAG
为 true
,则执行某些操作:
public static boolean FLAG = false;
public static void main(String[] args) throws InterruptedException {
while (true) {
Thread.sleep(3000); // 每隔3秒检查一次标记量
if (FLAG) {
doSomething();
break;
}
}
}
public static void doSomething() {
System.out.println("Hello World!!!");
}
在这段代码中,使用 Thread.sleep(3000)
来避免频繁检查标记量的值,看起来非常合理对吧。然而,这种做法会导致性能问题,IDEA 会给出警告:
Call to ‘Thread.sleep()’ in a loop, probably busy-waiting
在循环中调用 Thread.sleep(),可能会导致 busy-waiting(忙等待)
原因分析
调用 Thread.sleep()
时,操作系统需要对线程进行挂起和唤醒操作,每次休眠后线程都需要被恢复到运行状态。这种频繁的线程状态切换会导致严重的性能开销,特别是在短时间间隔内(如 3 秒)。这不仅会浪费 CPU 资源,还可能导致应用的响应延迟增加,甚至在高负载的情况下引发性能瓶颈。
解决方案
如果对文中示例有所了解,你会发现本质其实就是一个定时问题,每隔一段时间进行操作直至达到条件,按文中示例,便是每隔 3 秒检查标记量并做一些事情,因此我们完全可使用调度 API 进行替换。以下是几种常见的替代方案:
Timer
和 TimerTask
Timer
和 TimerTask
提供了简单的定时任务调度机制,是 Java 中用于调度定时任务的老牌工具。虽然在多线程环境下并不如 ScheduledExecutorService
强大,但它仍然是一种简单有效的定时任务解决方案。
代码示例:
import java.util.Timer;
import java.util.TimerTask;
public class Main {
public static boolean FLAG = false;
public static void main(String[] args) {
// 创建定时器
Timer timer = new Timer();
// 定时任务,每隔3秒检查一次
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
解析:
Timer.scheduleAtFixedRate()
:该方法能够以固定频率调度任务。在本例中,我们设置了每 3 秒执行一次任务。取消定时任务:任务完成后,我们调用 timer.cancel()
来停止定时器,防止任务继续执行。
优势:
简洁易用: Timer
和TimerTask
的使用简单,适用于轻量级的定时任务。
缺点:
不支持多线程任务调度: Timer
适用于单线程环境,对于多线程任务调度时,它的表现不如ScheduledExecutorService
。无法处理任务异常: Timer
不能捕捉任务中的异常,任务异常可能导致后续的定时任务停止。
ScheduledExecutorService
ScheduledExecutorService
是 Java 5 引入的一个更为现代的调度接口,它相较于 Timer
更加灵活和强大,能够更好地处理多线程任务。
代码示例:
import java.util.concurrent.*;
public class Main {
public static boolean FLAG = false;
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
if (FLAG) {
doSomething();
scheduler.shutdown(); // 任务完成后关闭调度器
}
}, 0, 3, TimeUnit.SECONDS); // 延迟0秒后每3秒执行一次
}
public static void doSomething() {
System.out.println("Hello World!!!");
}
}
解析:
ScheduledExecutorService
:该接口支持定时任务的调度,可以避免频繁的线程上下文切换。通过scheduleAtFixedRate()
方法,每 3 秒执行一次任务,直到FLAG
为true
。任务管理:调度器自动管理任务执行,无需显式控制线程的休眠和唤醒,避免了忙等待的问题。
优势:
多线程支持: ScheduledExecutorService
适用于多线程环境,并且线程池的使用能够有效减少线程创建和销毁的开销。
缺点:
适用性:相比 Timer
,ScheduledExecutorService
在代码上稍显复杂,尤其在简单的定时任务中,可能稍显复杂。
Object.wait()
和 Object.notify()
在多线程环境中,可以使用 Object.wait()
和 Object.notify()
方法来进行线程间的协调。通过这种方式,线程可以等待特定条件的变化,而无需频繁轮询和消耗 CPU 资源。
代码示例:
public class Main {
private static final Object lock = new Object(); // 用于同步的锁对象
public static boolean FLAG = false;
public static void main(String[] args) throws InterruptedException {
// 创建并启动检查线程
Thread checkerThread = new Thread(() -> {
synchronized (lock) {
while (!FLAG) {
try {
lock.wait(3000); // 等待3秒或 FLAG 被设置为 true
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
doSomething();
}
});
checkerThread.start();
// 模拟一些其他工作,最后设置 FLAG 为 true
Thread.sleep(5000); // 等待5秒钟
FLAG = true;
// 通知等待的线程可以继续执行
synchronized (lock) {
lock.notify(); // 唤醒等待线程
}
}
public static void doSomething() {
System.out.println("Hello World!!!");
}
}
解析:
Object.wait()
:线程在lock
对象上调用wait()
,进入等待状态,直到FLAG
为true
或超时(在此示例中为 3 秒)。Object.notify()
:当FLAG
变为true
时,主线程调用notify()
唤醒等待的线程。
优势:
线程间协调:利用 wait()
和notify()
机制,线程可以高效地等待条件的变化,而不需要消耗 CPU 资源。适用于多线程环境:这种方式适合在多个线程间进行协调和同步,避免了繁琐的手动控制。
缺点:
锁的管理:需要小心避免死锁和竞争条件。线程同步管理相对复杂。 可能的超时问题:在使用 wait()
时,必须谨慎设置超时,否则线程可能会长时间处于阻塞状态。
CompletableFuture
CompletableFuture
是 Java 8 引入的一个强大的异步编程工具。它可以方便地处理异步任务,并允许在任务完成时触发回调,避免阻塞等待。
代码示例:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class Main {
public static boolean FLAG = false;
public static void main(String[] args) {
// 异步检查 FLAG 并执行操作
CompletableFuture.runAsync(() -> {
while (!FLAG) {
try {
TimeUnit.SECONDS.sleep(3); // 每隔3秒检查一次
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
doSomething();
});
// 模拟一些工作,稍后设置 FLAG 为 true
try {
TimeUnit.SECONDS.sleep(5); // 模拟延迟
FLAG = true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void doSomething() {
System.out.println("Hello World!!!");
}
}
解析:
CompletableFuture.runAsync()
:在后台线程中异步执行任务,检查FLAG
的状态并执行操作。异步控制:任务会每 3 秒检查一次 FLAG
,一旦FLAG
为true
,就执行doSomething()
方法。
优势:
异步编程: CompletableFuture
提供了强大的异步任务处理能力,避免了阻塞等待。链式操作:支持任务的链式调用和回调机制,便于复杂任务的管理。
缺点:
适用场景有限:适合异步任务管理,对于简单的定时任务可能有些复杂。 调试难度:异步任务的调试可能较为复杂,需要更精细的控制。
推荐阅读: