Busy Spin
Complete Step-by-Step Guide
Introduction
In concurrent programming, Busy Spin is a situation where a thread repeatedly checks for a condition without yielding control, consuming CPU cycles unnecessarily. While this approach reduces thread latency, it can cause high CPU utilization and inefficiency.
In the Producer-Consumer problem, busy spin occurs when either producers or consumers continuously check whether the queue is full or empty without proper synchronization.
In this blog, we will:
- Understand what busy spin is.
- Implement a producer-consumer problem using busy spin.
- Identify its drawbacks.
- Implement an optimized solution.
Step 1 — Understanding Busy Spin
Busy spinning happens when threads are waiting actively instead of sleeping or yielding control.
Example:
while (!condition) {
// do nothing, just keep checking
}
Why it happens:
In a poorly synchronized producer-consumer scenario, threads may constantly check the queue’s state instead of waiting efficiently.
Impact:
- Wastes CPU resources.
- Leads to inefficiency in multithreaded systems.
- Increases power consumption.
Step 2 — Implementing Producer-Consumer with Busy Spin
Let’s implement a producer-consumer system without proper blocking to illustrate busy spin.
Shared Queue Implementation
import java.util.LinkedList;
class BusySpinQueue {
private final LinkedList list = new LinkedList<>();
private final int capacity;
public BusySpinQueue(int capacity) {
this.capacity = capacity;
}
public void put(T item) {
while (list.size() == capacity) {
// Busy spin — continuously checking without waiting
}
synchronized (list) {
list.add(item);
}
}
public T take() {
while (list.isEmpty()) {
// Busy spin — continuously checking without waiting
}
synchronized (list) {
return list.removeFirst();
}
}
}
Step 3 — Producer and Consumer Threads
Producer:
class BusySpinProducer implements Runnable {
private final BusySpinQueue queue;
public BusySpinProducer(BusySpinQueue queue) {
this.queue = queue;
}
@Override
public void run() {
int value = 1;
while (true) {
queue.put(value);
System.out.println(Thread.currentThread().getName() + " producing: " + value++);
}
}
}
Consumer:
class BusySpinConsumer implements Runnable {
private final BusySpinQueue queue;
public BusySpinConsumer(BusySpinQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
Integer value = queue.take();
System.out.println(Thread.currentThread().getName() + " consuming: " + value);
}
}
}
Step 4 — Testing Busy Spin Implementation
public class BusySpinTest {
public static void main(String[] args) {
BusySpinQueue queue = new BusySpinQueue<>(3);
Thread producer = new Thread(new BusySpinProducer(queue), "Producer");
Thread consumer = new Thread(new BusySpinConsumer(queue), "Consumer");
producer.start();
consumer.start();
}
}
Step 5 — Drawbacks of Busy Spin
While this implementation works, it is highly inefficient:
- Constant CPU usage: Threads consume CPU cycles while doing nothing productive.
- Waste of resources: Increases power consumption.
- Scalability issues: Under heavy load, it can degrade system performance drastically.
- No fairness: Threads can hog CPU, starving others.
Step 6 — Solving Busy Spin Problem
Instead of busy spin, we should use proper synchronization with wait() and notify(), as in CustomLinkedBlockingQueue.
Optimized Queue Implementation
class BlockingQueue {
private final LinkedList list = new LinkedList<>();
private final int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T item) throws InterruptedException {
while (list.size() == capacity) {
wait();
}
list.add(item);
notifyAll();
}
public synchronized T take() throws InterruptedException {
while (list.isEmpty()) {
wait();
}
T item = list.removeFirst();
notifyAll();
return item;
}
}
This approach eliminates busy spin by making threads wait instead of looping endlessly.
Step 7 — Producer and Consumer with BlockingQueue
Producer:
class BlockingProducer implements Runnable {
private final BlockingQueue queue;
public BlockingProducer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
int value = 1;
while (true) {
try {
queue.put(value);
System.out.println(Thread.currentThread().getName() + " producing: " + value++);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Consumer:
class BlockingConsumer implements Runnable {
private final BlockingQueue queue;
public BlockingConsumer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
Integer value = queue.take();
System.out.println(Thread.currentThread().getName() + " consuming: " + value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Step 8 — Testing Optimized Consumer-Producer
public class BlockingTest {
public static void main(String[] args) {
BlockingQueue queue = new BlockingQueue<>(3);
Thread producer = new Thread(new BlockingProducer(queue), "Producer");
Thread consumer = new Thread(new BlockingConsumer(queue), "Consumer");
producer.start();
consumer.start();
}
}
This optimized implementation prevents busy spin and ensures efficient CPU usage, fairness, and thread safety.
Summary
Busy spin is a concurrency problem where threads continuously check a condition without pausing, consuming unnecessary CPU cycles. While busy spin can reduce latency in some low-latency applications, it is generally inefficient for most real-world scenarios, especially in producer-consumer problems.
We explored a busy spin-based producer-consumer implementation, then identified its drawbacks — high CPU usage, waste of resources, scalability issues, and poor fairness. We then implemented a better solution using wait() and notify(), which made the queue block threads efficiently without wasting CPU cycles.
This exercise illustrates the importance of proper synchronization in multithreaded systems. Eliminating busy spin not only improves efficiency but also ensures better scalability and reliability. For production-ready systems, Java’s built-in LinkedBlockingQueue is the optimal choice because it handles these concerns internally, allowing developers to focus on business logic instead of concurrency pitfalls.
