Busy Waiting in Multithreading in Java

2025年3月26日 | 阅读 4 分钟

通过一种名为“忙等待”的多线程方法,一个线程在不放弃 CPU 控制权的情况下,会持续等待某个条件得到满足。由于线程在等待时会主动占用 CPU 周期,因此这种策略可能导致 CPU 利用率低下。

如果一个 Java 线程 持续循环直到某个条件满足,它就可能经历忙等待。由于其资源效率低下,尽管有时是必需的,但与替代的同步机制相比,它通常被视为一种较差的技术。

忙等待

当一个线程处于忙等待状态时,它会在一个循环中持续检查一个条件,而没有任何增值操作。在条件满足之前,线程会一直运行并消耗 CPU 资源。相比之下,在更有效的等待系统中,线程要么被停止,要么被休眠,直到收到显式通知。

示例

输出

 
Waiting thread started...
Condition changed to true.
Condition met. Exiting waiting loop.   

在这种情况下,`waitingThread` 通过不断监视 `conditionMet` 变量的值来执行忙等待。当 `conditionChangerThread` 将 `conditionMet` 设置为 `true` 时,`waitingThread` 会跳出循环。

忙等待的特点

  1. CPU 密集型:当循环持续时间很长时,等待的线程会主动占用 CPU 资源,这可能导致过高的 CPU 利用率和系统性能下降。
  2. 易于实现:实现忙等待只需要一个不断检查条件的循环。
  3. 低延迟响应:当情况发生变化时,等待的线程可能反应非常迅速,因为它一直在监视情况。

忙等待的缺点

尽管忙等待可以提供对条件变化进行响应的极低延迟,但由于一系列缺点,它通常被认为效率低下。

  1. 高 CPU 使用率:由于等待的线程一直在运行,忙等待可能导致高 CPU 使用率。它可能影响其他程序和进程的有效性。
  2. 资源利用率低下:线程在等待期间不会产生任何有价值的工作。因此,CPU 周期被浪费了,而这些周期可以用于其他地方。
  3. 可伸缩性问题:在多线程环境中,当多个线程忙于等待时,资源浪费会累积,导致可伸缩性差,甚至可能导致系统崩溃。

忙等待的替代方案

为了避免忙等待的低效率,Java 提供了多种同步机制,允许线程更有效地等待。

1. 使用 wait() 和 notify() 方法

作为 Java Object 类 的一部分,`wait()` 和 `notify()` 方法允许线程等待,并在条件发生变化时得到通知。当我们调用 `wait()` 方法时,线程会释放它拥有的任何锁,并进入等待状态,直到另一个线程在同一个对象上调用 `notifyAll()` 或 `notify()` 方法。

2. 使用 java.util.concurrent 包

Java 的 `java.util.concurrent` 包提供了各种构造,可在不进行忙等待的情况下管理 同步

  • CountDownLatch:它允许一个或多个线程等待其他线程完成一系列操作。
  • CyclicBarrier:它使一组线程能够相互等待,直到到达一个共享屏障。
  • Semaphore:通过管理一组权限来控制对资源的访问。
  • Lock 和 Condition:比 `wait/notify` 和 `synchronized` 更通用、更强大的同步工具。

何时使用忙等待?

尽管忙等待效率低下,但在某些情况下可能是合理的。

低延迟要求:当等待时间非常短,并且线程需要快速响应条件变化时。

  1. 硬件交互:在某些底层程序中,为了直接与硬件通信,可能需要忙等待。
  2. 自旋锁:自旋锁是一种忙等待,旨在保护关键区域,因为这些区域应该有非常短的等待时间。

结论

在 Java 应用程序中,忙等待是一种简单但效率低下的线程同步方法,可能导致 CPU 使用率过高和性能下降。Java 提供了更有效的同步技术来帮助避免忙等待带来的问题,例如锁、条件以及 `wait()` 或 `notify()` 方法。


下一个主题Java JIT