Starvation

Imagine you’re invited to dine with the president or your favorite sports star, or say a natural calamity strikes. Will it change your existing engagements for the evening? There’s a very high probability that it would. Similarly, all application and OS threads that execute on a system are assigned a priority (default or explicit). Usually threads with a higher priority are preferred to execute by the thread scheduler. But this preference might leave threads with a lower priority starved to be scheduled.

A thread can also starve to be scheduled when it’s waiting to acquire a lock on an object monitor that has been acquired by another thread that usually takes long to execute and is invoked frequently.

Thread scheduling is dependent on an underlying OS. Usually all OSs support prioritized scheduling of high-priority threads, but this behavior isn’t guaranteed across different platforms.

Starvation describes a situation where a greedy thread holds a resource for a long time so other threads are blocked forever. The blocked threads are waiting to acquire the resource but they never get a chance. Thus they starve to death.

Starvation can occur due to the following reasons:

- Threads are blocked infinitely because a thread takes long time to execute some synchronized code (e.g. heavy I/O operations or infinite loop).

- A thread doesn’t get CPU’s time for execution because it has low priority as compared to other threads which have higher priority.

- Threads are waiting on a resource forever but they remain waiting forever because other threads are constantly notified instead of the hungry ones.When a starvation situation occurs, the program is still running but doesn’t run to completion because some threads are not executed.

A Starvation Example:

Let’s see an example. Suppose we have a Worker class like this:

package com.gs.iilp.corejava.threads;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

class Worker {

	public synchronized void work() {
		String name = Thread.currentThread().getName();
		String fileName = name + ".txt";

		try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
			writer.write("Thread " + name + " wrote this mesasge");
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		while (true) {
			System.out.println(name + " is working");

		}
	}
}

public class StarvationExample {
	public static void main(String[] args) {
		Worker worker = new Worker();
		for (int i = 0; i < 10; i++) {
			new Thread(new Runnable() {
				public void run() {
					worker.work();
				}
			}).start();
		}
	}
}

This class has a synchronized method work() that creates a text file <thread-name>.txt and writes a message to it. Then it repeatedly prints a message:

And the following program creates 10 threads that call the work() method on a shared instance of the Worker class:

According to the code logic, each thread should create a text file with the name of <thread-name>.txt but you see only one gets created, e.g. thread-1.txt. That means other threads are unable to execute the work() method.

Why does this happen? It’s because the while loop runs forever so that the first executed thread never release the lock, causing other threads to wait forever.

A solution to solve this starvation problem is to make the current thread waits for a specified amount of time so other threads have chance to acquire the lock on the Worker object:

package com.gs.iilp.corejava.threads;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

class Worker {

	public synchronized void work() {
		String name = Thread.currentThread().getName();
		String fileName = name + ".txt";

		try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
			writer.write("Thread " + name + " wrote this mesasge");
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		while (true) {
			System.out.println(name + " is working");

			try {
				wait(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class StarvationExample {
	public static void main(String[] args) {
		Worker worker = new Worker();
		for (int i = 0; i < 10; i++) {
			new Thread(new Runnable() {
				public void run() {
					worker.work();
				}
			}).start();
		}
	}
}

Conclusion

So far I have helped you identify the 3 problems which can happen in multi-threading Java programs: deadlock, livelock and starvation. Livelock and starvation are less common than deadlock but they still can occur. To summarize, the following points help you understand the key differences of these problems:

- Deadlock: All threads are blocked, the program hangs forever.

- Livelock: No threads blocked but they run into infinite loops. The program is still running but unable to make further progress.

- Starvation: Only one thread is running, and other threads are waiting forever.You should be aware of these problems which can occur with multiple threads and synchronization, and design your programs to avoid them.

Last updated