This is the 8th day of my participation in the August Text Challenge.More challenges in August

When we encounter deadlocks, in addition to manually restart the program to solve the problem, we can also consider using sequential locks and polling locks, which can be referred to my last article. However, polling locks can cause serious new problems if not used properly, so in this article we will take a look at these problems and their solutions.

Problems demonstrate

When we did not use polling locks, we might have problems like this:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadLockByReentrantLock {
    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                lockA.lock(); / / lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 1: Waiting for B...");
                    lockB.lock(); / / lock
                    try {
                        System.out.println("Thread 1: acquired lock B!");
                    } finally {
                        lockA.unlock(); / / releases the lock}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock}}}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                lockB.lock(); / / lock
                System.out.println("Thread 2: Acquired lock B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 2: Waiting for A...");
                    lockA.lock(); / / lock
                    try {
                        System.out.println("Thread 2: Got lock A!");
                    } finally {
                        lockA.unlock(); / / releases the lock}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockB.unlock(); / / releases the lock}}}); t2.start();// Run the thread}}Copy the code

The result of the above code is as follows:

As you can see from the above results, threads are waiting for each other and trying to acquire each other’s resources. This is a typical deadlock problem.

Simple version of polling lock

After the deadlock problem, we can use polling lock to deal with it, its implementation approach by way of polling to obtain multiple locks, if there are any lock acquisition fail halfway, execute fallback, release of the current thread has all locks, waiting for the next to perform, so you can avoid multiple threads and grab the lock resources at the same time, To directly solve the deadlock problem, a simple version of the polling lock implementation is as follows:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample2 {
    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1(using polling lock)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Call the polling lockpollingLock(lockA, lockB); }}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                lockB.lock(); / / lock
                System.out.println("Thread 2: Acquired lock B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 2: Waiting for A...");
                    lockA.lock(); / / lock
                    try {
                        System.out.println("Thread 2: Got lock A!");
                    } finally {
                        lockA.unlock(); / / releases the lock}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockB.unlock(); / / releases the lock}}}); t2.start();// Run the thread
    }

    /** * polling lock */
    private static void pollingLock(Lock lockA, Lock lockB) {
        / / polling lock
        while (true) {
            if (lockA.tryLock()) { // Try to get the lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 1: Waiting for B...");
                    if (lockB.tryLock()) { // Try to get the lock
                        try {
                            System.out.println("Thread 1: acquired lock B!");
                        } finally {
                            lockB.unlock(); / / releases the lock
                            System.out.println("Thread 1: Release lock B.");
                            break; }}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock
                    System.out.println("Thread 1: Release lock A."); }}// Wait one second before continuing
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The result of the above code is as follows:

We can see from the above results, when we use the polling lock in the program, there is no deadlock problem, but the above polling lock is not perfect, let’s look at the polling lock will have some problems.

Problem 1: Endless loops

The above simple version of the polling lock, if a thread has been holding the lock resource or for a long time, will cause the polling lock to enter an infinite loop state, it will try to keep holding the lock resource, which will cause new problems and unnecessary performance overhead, as shown in the following example.

counter-examples

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample {

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1(using polling lock)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Call the polling lockpollingLock(lockA, lockB); }}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                lockB.lock(); / / lock
                System.out.println("Thread 2: Acquired lock B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 2: Waiting for A...");
                    lockA.lock(); / / lock
                    try {
                        System.out.println("Thread 2: Got lock A!");
                    } finally {
                        lockA.unlock(); / / releases the lock}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // Thread 2 has not released the lock resource if this code is not executed
                    // lockB.unlock(); }}}); t2.start();// Run the thread
    }

    /** * polling lock */
    public static void pollingLock(Lock lockA, Lock lockB) {
        while (true) {
            if (lockA.tryLock()) { // Try to get the lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 1: Waiting for B...");
                    if (lockB.tryLock()) { // Try to get the lock
                        try {
                            System.out.println("Thread 1: acquired lock B!");
                        } finally {
                            lockB.unlock(); / / releases the lock
                            System.out.println("Thread 1: Release lock B.");
                            break; }}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock
                    System.out.println("Thread 1: Release lock A."); }}// Wait one second before continuing
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The result of the above code is as follows:As can be seen from the above results, thread 1 polling lock entered the state of an infinite loop.

Optimized version

In view of the above dead-loop situation, we can improve the following two ideas:

  1. Add the maximum number of times limit: If no lock is obtained after n attempts, the lock is considered to have failed to be obtained and the polling is terminated after implementing the failed policy (logging or other operations can be used).
  2. Add maximum duration limit: If no lock is obtained after n seconds, the lock is considered to have failed to be obtained and the polling is terminated after the failed policy is executed.

Either of the above strategies can solve the problem of infinite loop. Considering the implementation cost, we can adopt the mode of polling maximum times to improve polling lock, and the specific implementation code is as follows:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample {

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1(using polling lock)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Call the polling lock
                pollingLock(lockA, lockB, 3); }}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                lockB.lock(); / / lock
                System.out.println("Thread 2: Acquired lock B!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 2: Waiting for A...");
                    lockA.lock(); / / lock
                    try {
                        System.out.println("Thread 2: Got lock A!");
                    } finally {
                        lockA.unlock(); / / releases the lock}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    Thread 2 forgot to release the lock resource
                    // lockB.unlock(); / / releases the lock}}}); t2.start();// Run the thread
    }

    /** * polling lock ** maxCount: maximum polling number */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // Count the number of polls
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // Try to get the lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(1000);
                    System.out.println("Thread 1: Waiting for B...");
                    if (lockB.tryLock()) { // Try to get the lock
                        try {
                            System.out.println("Thread 1: acquired lock B!");
                        } finally {
                            lockB.unlock(); / / releases the lock
                            System.out.println("Thread 1: Release lock B.");
                            break; }}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock
                    System.out.println("Thread 1: Release lock A."); }}// Determine whether the maximum number has been exceeded
            if (count++ > maxCount) {
                // Terminate the loop
                System.out.println("Failed to obtain polling lock, log or execute other failed policy");
                return;
            }

            // Wait one second before continuing to try to acquire the lock
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The result of the above code is as follows:

From the above results, we can see that when we improve the polling lock, it does not have the problem of endless loop, it will try a certain number of times to terminate execution.

Problem 2: Threads starve to death

The polling wait time for our polling lock above is fixed time, as shown in the following code:

// Wait 1s before trying to acquire the lock
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Copy the code

This can cause thread starvation in special cases where the polling lock never gets the lock, as in the following example.

counter-examples

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample {

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1(using polling lock)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Call the polling lock
                pollingLock(lockA, lockB, 3); }}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                while (true) {
                    lockB.lock(); / / lock
                    System.out.println("Thread 2: Acquired lock B!");
                    try {
                        System.out.println("Thread 2: Waiting for A...");
                        lockA.lock(); / / lock
                        try {
                            System.out.println("Thread 2: Got lock A!");
                        } finally {
                            lockA.unlock(); / / releases the lock}}finally {
                        lockB.unlock(); / / releases the lock
                    }
                    // Wait one second to resume execution
                    try {
                        Thread.sleep(1000);
                    } catch(InterruptedException e) { e.printStackTrace(); }}}}); t2.start();// Run the thread
    }

    /** * polling lock */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // loop count
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // Try to get the lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(100); // Wait 0.1s for the lock
                    System.out.println("Thread 1: Waiting for B...");
                    if (lockB.tryLock()) { // Try to get the lock
                        try {
                            System.out.println("Thread 1: acquired lock B!");
                        } finally {
                            lockB.unlock(); / / releases the lock
                            System.out.println("Thread 1: Release lock B.");
                            break; }}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock
                    System.out.println("Thread 1: Release lock A."); }}// Determine whether the maximum number has been exceeded
            if (count++ > maxCount) {
                // Terminate the loop
                System.out.println("Failed to obtain polling lock, log or execute other failed policy");
                return;
            }

            // Wait one second before continuing to try to acquire the lock
            try {
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The result of the above code is as follows:

As can be seen from the above results, thread 1 (polling lock) has not succeeded in acquiring the lock. The reason for this result is as follows: Thread 1 has a fixed waiting time of 1s for each poll, while thread 2 also has the same frequency, acquiring the lock every 1s. As a result, thread 2 will always successfully acquire the lock first, while thread 1 will always starve to death, as shown in the following figure:

Optimized version

Next, we can improve the fixed waiting time of polling lock to fixed time + random time, so as to avoid the problem of polling lock “starvation” caused by the consistent frequency of acquiring locks. The specific implementation code is as follows:

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SolveDeadLockExample {
    private static Random rdm = new Random();

    public static void main(String[] args) {
        Lock lockA = new ReentrantLock(); // Create lock A
        Lock lockB = new ReentrantLock(); // Create lock B

        // Create thread 1(using polling lock)
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // Call the polling lock
                pollingLock(lockA, lockB, 3); }}); t1.start();// Run the thread

        // Create thread 2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run(a) {
                while (true) {
                    lockB.lock(); / / lock
                    System.out.println("Thread 2: Acquired lock B!");
                    try {
                        System.out.println("Thread 2: Waiting for A...");
                        lockA.lock(); / / lock
                        try {
                            System.out.println("Thread 2: Got lock A!");
                        } finally {
                            lockA.unlock(); / / releases the lock}}finally {
                        lockB.unlock(); / / releases the lock
                    }
                    // Wait one second to resume execution
                    try {
                        Thread.sleep(1000);
                    } catch(InterruptedException e) { e.printStackTrace(); }}}}); t2.start();// Run the thread
    }

    /** * polling lock */
    public static void pollingLock(Lock lockA, Lock lockB, int maxCount) {
        // loop count
        int count = 0;
        while (true) {
            if (lockA.tryLock()) { // Try to get the lock
                System.out.println("Thread 1: got lock A!");
                try {
                    Thread.sleep(100); // Wait 0.1s for the lock
                    System.out.println("Thread 1: Waiting for B...");
                    if (lockB.tryLock()) { // Try to get the lock
                        try {
                            System.out.println("Thread 1: acquired lock B!");
                        } finally {
                            lockB.unlock(); / / releases the lock
                            System.out.println("Thread 1: Release lock B.");
                            break; }}}catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lockA.unlock(); / / releases the lock
                    System.out.println("Thread 1: Release lock A."); }}// Determine whether the maximum number has been exceeded
            if (count++ > maxCount) {
                // Terminate the loop
                System.out.println("Failed to obtain polling lock, log or execute other failed policy");
                return;
            }

            // Wait a certain amount of time (fixed time + random time) before continuing to try to acquire the lock
            try {
                Thread.sleep(300 + rdm.nextInt(8) * 100); // Fixed time + random time
            } catch(InterruptedException e) { e.printStackTrace(); }}}}Copy the code

The result of the above code is as follows:

As can be seen from the above results, thread 1 (polling lock) will not starve to death after adding random wait time.

conclusion

In this paper, we introduced the polling of the use of locks, used to solve the problem of deadlock, but the simple version of the polling lock in some cases can cause death cycle and thread starvation problem, so we lock polling is optimized, to lock polling joined the largest number of polling, and random polling wait time, so we can solve for the introduction of the new problems caused by polling the lock, This makes it fun to use it to solve deadlocks.

Reference & acknowledgements

Java Concurrent Programming

Concurrent original articles are recommended

  1. Thread 4 ways to create and use details!
  2. Is there a big difference between user threads and daemon threads in Java?
  3. Understand thread pools in depth
  4. 7 ways to create a thread pool, highly recommended…
  5. How great is pooling technology? I was shocked to see the comparison between threads and thread pools!
  6. Thread synchronization and locking in concurrency
  7. Synchronized “this” and “class”
  8. The difference between volatile and synchronized
  9. Is a lightweight lock faster than a weightweight lock?
  10. How can terminating the thread cause the service to go down?
  11. SimpleDateFormat 5 Solutions to Thread Insecurity!
  12. ThreadLocal not working well? That’s you!
  13. ThreadLocal memory overflow code
  14. Semaphore confessions: I’m the one with the restrictor!
  15. CountDownLatch: Wait till you latch on!
  16. CyclicBarrier: Once the driver has finished, he can start the train!
  17. Synchronized optimization means of lock expansion mechanism!
  18. Four optimizations in Synchronized. How many do you know?
  19. 4 pits in ReentrantLock!
  20. Why do unfair locks perform better?
  21. Deadlock detection tools for 4!
  22. Deadlock terminator: Sequential locking and polling locking!

Follow “Java Chinese Community” for more interesting and informative Java concurrent articles.