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

In the previous article, we introduced the concept of deadlock and its causes. This article will introduce you to several common deadlock prevention strategies.

In short, there are three main strategies for preventing deadlocks:

  • Sequential locking;
  • Give the lock a timeout period;
  • Detect deadlocks.

First, sequential locking

Deadlocks are usually caused by unordered requests for resources from multiple threads. Resources are limited and it is not possible to satisfy all threads at the same time. However, if each thread’s request can be satisfied in a certain order, then deadlocks no longer exist. This is known as a Lock Ordering.

To take a more mundane example, you must have encountered annoying traffic jams at intersections. The reason for the traffic jam at the intersection, because there are too many cars, everyone is scrambling to go in their own direction, each other, not to block the strange. At this time, the need for traffic police in the middle of the coordination and command, relieve congestion. The fundamental reason why the traffic police can relieve congestion is that the traffic police make the original disorderly and competitive traffic flow into an orderly queue.

The same number of cars, the intersection is the same intersection, but the road is clear, which is similar to thread lock contention.

In the deadlock code in the previous article, thread 1 and thread 2 took possession of A and B first, respectively, causing the deadlock. If thread 1 and thread 2 both occupy A and then compete for B, the deadlock will not occur.

Define Nezha thread 1, preempt A and then fight B:

Private static class implements Runnable {public void run() {System.out.println(); private static class implements Runnable {public void run() {System.out.println(); ); try { Thread.sleep(10); } catch (interruptedException) {} System.out.println(" Nezha: wait for B...") {} InterruptedException ignored) {} System.out.println(" Nezha: wait for B...") ); Synchronized (lockB) {System.out.println(" Nezha: Has both A and B..." ); }}}}

Thread 2 is defined as preemption A and then preemption B, which is different from before:

private static class LanLingWang implements Runnable { public void run() { synchronized(lockA) { System.out.println(" Lanling King: Hold A!" ); try { Thread.sleep(10); } catch (interruptedException) {} System.out.println("... ); Synchronized (lockB) {System.out.println(" Synchronized (lockB) {System.out.println(" Synchronized (lockB) {System.out.println(" Synchronized (lockB); ); }}}}

Start two threads:

public class DeadLockDemo { public static final Object lockA = new Object(); public static final Object lockB = new Object(); public static void main(String args[]) { Thread thread1 = new Thread(new NeZha()); Thread thread2 = new Thread(new LanLingWang()); thread1.start(); thread2.start(); }}

The output of the two threads is as follows:

Nezha: Hold A! Nezha: Waiting for B... Nezha: Already holds both A and B... Lanling King: Hold A! King Lanling: Wait for B... Lanling King: Already holds both A and B...

As you can see from the results of the run, both threads got the resources they needed one after the other without causing a deadlock.

Adjusting the locking order is a simple but effective deadlock prevention strategy. However, this strategy is not a panacea. It only works if you already know the sequence of locks when you code.

Give the lock a timeout period

In the previous article, we said that there are a number of conditions necessary for deadlock to occur, one of which is an infinite wait. The lock timeout is set to break this condition, making an infinite wait a finite wait.

Taking the previous code as an example, when Nezha and Lanling King are fighting for resources, the other thread does not give in to each other, leading to the deadlock of infinite waiting. At this point, if either party sets a time limit for waiting, the deadlock will be broken when the time is up, and the thread can still wait a little longer to try again.

Note that a synchronized block cannot specify a lock timeout. So, if you need a lock timeout, you need to use a custom lock, or use the concurrency utility classes provided by the JDK. The usage of the related tool classes will be described in a subsequent article, but will not be described in this article.

In addition, the idea of adding a timeout period to a lock has two meanings. One is in the request of the lock need to set the timeout time, the second is to obtain the lock after the lock to hold also want to have a timeout time, always can’t hand on not put, that is playing rogue.

Deadlock detection

As a third strategy for Deadlock prevention, you can think of Deadlock Detection as a more passive skill. Deadlock Detection is required when locks cannot be sequentially added or when the timeout for the locks cannot be set.

The core principle of deadlock detection lies in the data marking and tracking of threads and resources.

When a thread acquires a lock, the corresponding relationship between the lock and the thread is recorded in a Graph or Map data structure. In this way, the thread can analyze whether a deadlock exists by iterating over the recorded data when the lock is denied.

When a thread finds a deadlock, it can release the lock, wait a while and try again.

How to visually view thread deadlock and other states

When you feel that the thread may be blocked or deadlocked, you can check it with the jstack command. If there is a deadlock, the output will have an explicit deadlock prompt, as shown below:

$ jstack -F 8321
Attaching to process ID 8321, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 1.6.0-rc-b100
Deadlock Detection:

Found one Java-level deadlock:
=============================

"Thread2":
  waiting to lock Monitor@0x000af398 (Object@0xf819aa10, a java/lang/String),
  which is held by "Thread1"
"Thread1":
  waiting to lock Monitor@0x000af400 (Object@0xf819aa48, a java/lang/String),
  which is held by "Thread2"

Found a total of 1 deadlock.

In addition to jstack, Jprofiler is also a very powerful threading and stack analysis tool, and works well with IDEA and other IDEs.

With the help of Jprofiler, we can visually see the deadlock in the sample code above, as well as the state of the two threads being Blocked in the Thread Monitor.



It’s important to note that Jprofiler is a paid app that offers a free trial period of 10 days. Ten days is enough if you don’t have regular use needs, but just study. Of course, you can also consider using JConsole, JVisualVM, etc.

summary

That’s all for deadlock prevention strategies. In this article, we introduced three deadlock pre-release strategies. Each of the three strategies has its own advantages and disadvantages. In terms of practical application, the second one is more commonly used to set a timeout period for the lock, while the first and third ones are logically and technically difficult and focus more on understanding rather than practical application.

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

Teacher’s trial

  • throughjstackCommand to look at the deadlock and resolve it.

Extended reading and references

  • Deadlock Prevention
  • “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.