Welcome to King of Concurrency, this is the 12th article in the series.

In the previous article, we introduced the concept of deadlocks and their causes. In this article, we introduce you to some common deadlock prevention strategies.

In short, there are three main strategies to prevent deadlocks:

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

Sequence add lock

Typically, deadlocks occur when multiple threads request resources out of order. Resources are limited and it is impossible to satisfy all threads at the same time. However, if the requests of each thread are fulfilled in a certain order, then deadlocks no longer exist. This is called a Lock Ordering system.

To take a more mundane example, you must have encountered the annoying traffic jam at the intersection. The traffic jam at the intersection is because there are too many cars, and we all scramble to go in our own direction. At this time, traffic police need to coordinate in the middle command, relieve congestion. The fundamental reason why traffic police can relieve congestion is that they turn the flow of cars from disorderly competition into an orderly queue.

There are still so many cars, the intersection is still the same intersection, but the road is free, this is similar to the thread lock competition.

In the deadlock code in the previous article, thread 1 and thread 2 took possession of A and B first, respectively, resulting in A deadlock. In the same order, thread 1 and thread 2 both claim A and then claim B, and the deadlock will not occur.

Define Ne Zha thread 1 to preempt A and then B:

private static class NeZha implements Runnable {
  public void run(a) {
    synchronized(lockA) {
      System.out.println(Ne Zha: Hold A!);

      try {
        Thread.sleep(10);
      } catch (InterruptedException ignored) {}
      System.out.println(Nezha: Wait for B...);

      synchronized(lockB) {
        System.out.println(Ne Zha: Already hold both A and B...); }}}}Copy the code

Lanling King thread 2 is defined to preempt A and then B, which is different from before:

private static class LanLingWang implements Runnable {
  public void run(a) {
    synchronized(lockA) {
      System.out.println("King lanling: Hold A!");

      try {
        Thread.sleep(10);
      } catch (InterruptedException ignored) {}
      System.out.println("King lanling: Wait for B...");

      synchronized(lockB) {
        System.out.println("King Lanling: already holding both A and B..."); }}}}Copy the code

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(newLanLingWang()); thread1.start(); thread2.start(); }}Copy the code

The output for both threads is as follows:

Ne Zha: Hold A! Ne Zha: Wait for B... Ne Zha: Already hold both A and B... Lanling King: Hold A! King Lanling: Wait for B... Lanling King: Already holding both A and B...Copy the code

As you can see from the results of the run, both threads have successively obtained the resources they need 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 order of locking at code time.

Second, give lock a timeout period

In the last article, we said that there are some conditions necessary for a deadlock to occur, one of which is an infinite wait. The lock timeout is designed to break this condition and make an infinite wait a finite wait.

Take the previous code as an example. When Nezha and King Lanling are competing for resources, the other two threads do not give way to each other, resulting in the deadlock of infinite waiting. At this point, if either party sets a deadline for waiting, the deadlock will be resolved when the time is up and the thread can still wait a little longer and try again.

Note that synchronized blocks cannot specify lock timeouts. So, if you need a lock timeout, you need to use custom locks or use the concurrency utility classes provided by the JDK. The usage of related utility classes will be described in future articles.

In addition, the so-called lock to add a timeout period, actually has two meanings. It is to need setting timeout time when request lock, 2 it is to be in after acquiring lock to also want to have timeout time to lock hold, total cannot come to the hand do not put, that is play rascal.

Deadlock detection

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

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

When a thread acquires a lock, the corresponding relationship between the lock and the thread is recorded through data structures such as Graph or Map. This way, if the lock is denied, the thread can analyze the deadlock by walking through the recorded data.

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

How to visually view thread deadlocks and other states

If you feel a thread might be blocked or deadlocked, use the jstack command to check. If a deadlock exists, 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.
Copy the code

In addition to JStack, JProfiler is a very powerful thread and stack analysis tool that works well with ides such as IDEA.

With the help of JProfiler, we can see the deadlocks in the above sample code very intuitively, and we can also see in Thread Monitor that two threads are blocked in the state.

It’s important to note that JProfiler is paid software and offers a free trial period of ten days. Ten days is enough if you don’t need it for regular use, but just for study. Of course, you can also consider using jConsole, jVisualvm, etc.

summary

That’s all for deadlock prevention strategies. In this article, we introduce three deadlock predispatch strategies. Each of the three strategies has its advantages and disadvantages. In terms of practical application, the second one is more commonly used, while the first one and the third one have certain logical and technical difficulties, focusing more on understanding rather than practical application.

This is the end of the text, congratulations you on a star ✨

The test of the master

  • throughjstackCommand to view deadlocks and resolve them.

Extensive reading and references

  • Deadlock Prevention
  • “King concurrent class” outline and update progress overview

About the author

Follow technology 8:30am for updates. Deliver high-quality technical articles, push author quality original articles at 8:30 in the morning, push industry in-depth good articles at 20:30 at night.

If this article has been helpful to you, please like it, follow it, and monitor it as we go from bronze to King.