Welcome to Concurrency King Lessons, the 13th article in a series.

In the previous article, we looked at several strategies for avoiding deadlocks. Deadlocks have a bad reputation, but in concurrent programming, there are some equally important thread activity issues that deserve attention. They are not well-known, but they are very destructive, and this article is about thread hunger and live locking.

First, the generation of hunger

What is known as threaded hunger (meredios) refers to the fact that in a multi-threaded competition for resources, voracious threads are locked into resources while other threads are in a waiting state, where the wait is fruitless and they starve to death.

The greed of the monopolist is one of the causes of hunger. Generally speaking, hunger is generally caused by the following three reasons:

(1) The thread is blocked indefinitely

When the thread acquiring the lock needs to perform an operation of infinite time (such as IO or an infinite loop), subsequent threads will be blocked indefinitely and starve to death.

(2) Thread priority reduction does not gain CPU time

When multiple competing threads are prioritized, the higher the priority, the more CPU time the thread is given. In some extreme cases, a low-priority thread may never be granted sufficient CPU time and starve to death.

(3) Threads are always waiting for resources

In the Bronze series, we said that Notify does not wake up a given thread when it sends notifications. When multiple threads are in a wait, some threads may never be notified and may starve.

Hunger and justice

To visualize thread hunger, we created the following code.

Create four hero players, including Nezha and King Lanling. They fight wild in a competitive way and kill wild monsters to gain economic benefits.

public class StarvationExample { public static void main(String[] args) { final WildMonster wildMonster = new WildMonster(); String[] players = {" Ne Zha ", "Ranling King "," Wei ", "Dian Wei"}; for (String player: players) { Thread playerThread = new Thread(new Runnable() { public void run() { wildMonster.killWildMonster(); }}); playerThread.setName(player); playerThread.start(); }}}
public class WildMonster { public synchronized void killWildMonster() { while (true) { String playerName = Thread.currentThread().getName(); Println (playerName + "Get the monster!" ); try { Thread.sleep(500); } catch (interruptedException e) {System.out.println(interruptedException e);} interrupt (interruptedException e) {System.out.println(interruptedException e); }}}}

The running results are as follows:

Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Ne Zha wins the monster! Process finished with exit code 130 (interrupted by signal 2: SIGINT)

As you can see from the results, in several threads of running, only Nezha was able to kill the monsters, while the other heroes were helpless and waiting to starve to death. Why did this happen?

If you look closely at the code in the Wildmonster class, the problem is in the KillWildmonster synchronization method. Once a hero enters the method, the object lock is held and other threads are blocked from re-entering.

Of course, the solution is as simple as breaking up the exclusivity. For example, if we change Thread.sleep to wait in the code below, the problem will be solved.

public static class WildMonster { public synchronized void killWildMonster() { while (true) { String playerName = Thread.currentThread().getName(); Println (playerName + "Get the monster!" ); try { wait(500); } catch (interruptedException e) {System.out.println(interruptedException e);} interrupt (interruptedException e) {System.out.println(interruptedException e); }}}}

The running results are as follows:

Ne Zha wins the monster! Armour beheaded wild monster! King Lanling beheaded the wild monster! Dianwei behead wild strange! King Lanling beheaded the wild monster! Dianwei behead wild strange! Process finished with exit code 130 (interrupted by signal 2: SIGINT)

As can be seen from the results, the four heroes all got the chance to play wild, which achieved fairness to a certain extent. (Note: Wait releases locks, but Sleep does not. If you don’t understand this, check out the Bronze article.)

How to make threads compete fairly is an important topic in threading problem. Although we can’t guarantee 100% fairness, we still need to design certain data structures and use appropriate utility classes to increase fairness between threads.

As for fairness between threads, it’s important to understand its existence and importance in this article, and we’ll cover the concurrency utility classes in a future article on how to do this gracefully.

Three, the trouble of live lock

You may not be as familiar with live locks as deadlocks. However, live locks are no less harmful than deadlocks. As a result, both live locks and deadlocks can be disastrous, causing the application to fail to provide normal service capabilities.

LiveLock is when two threads are busy responding to each other’s requests, but do nothing of their own. They keep repeating specific code and get nowhere.

Unlike deadlocks, live locks do not cause threads to enter a blocked state, but they spin in circles, so similar in effect to a deadlock, the program enters a wireless loop and cannot continue.

If you don’t have a visual understanding of what a live lock is, you’ve probably come across the following when you’re walking. The two men walked towards each other, out of courtesy they gave way to each other, let go back and forth, still unable to pass. Livelock, that’s what it means.

summary

That’s all for thread hunger and live locks. In this article, we introduced the causes of thread hunger. There is no 100% solution to thread hunger, but you can do as much as possible to achieve a level playing field. We did not list some of the utility classes for thread fairness in this article, because I think understanding the problem is more important than the solution. If there is no understanding of the problem, the scheme will appear when landing but do not know why the situation. In addition, while livelocks are not as well known as deadlocks, a proper understanding of livelocks is still necessary as part of the concurrency knowledge.

The text ends here, congratulations you went up a star ✨ again

Teacher’s trial

  • Write code to set the priority of different threads, experience thread hunger and give solutions.

Extended reading and references

  • Dynamic picture reference
  • “Concurrent king lesson” outline and update progress overview

About the author

Pay attention to the public number [mediocre technology joke], access to timely article updates. Record the technical stories of ordinary people, share quality (as much as possible) technical articles, and occasionally talk about life and ideals. No anxiety peddling, no headline peddling.

If this article is helpful to you, welcome thumb up, follow, monitor, we together from bronze to king.