HOLIDAY SALE! Save 50% on Membership with code HOLIDAY50. Save 15% on Mentorship with code HOLIDAY15.

4) Multithreading Lesson

Thread Livelock

11 min to complete · By Ryan Desmond

A livelock in Java will force two or more threads to enter an infinite loop and, therefore, stop their execution.

How Does a Livelock Work?

A livelock is similar to a deadlock but with a piece of extra logic that will release the lock using a timer, but the result is the same. 

The image below shows a livelock in action:

A graphic example of a livelock in Java between threads T1 and T2 trying to access resources A and B.

T1 successfully locked (or synchronized) resource A and is waiting for resource B to be unlocked. Meanwhile, T2 successfully locked (or synchronized) resource B and is waiting for resource A to be unlocked. 

After N milliseconds, this loop is produced:

  1. T1 rolls back and starts over with resource B
  2. T2 rolls back and starts over with resource A
  3. The situation repeats over and over. 

While the thread's execution is never truly blocked indefinitely, the program cannot move forward since the threads in a livelock enter into an infinite loop of repeated execution.

Take a look at the code below, which creates this livelock in a program.

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

public class LiveLock {

  // Create two Locks
  // Lock functionality is similar to synchronized block,
  // but it provides explicit lock/unlock methods
  private static Lock lock1 = new ReentrantLock(true);
  private static Lock lock2 = new ReentrantLock(true);

  public static void main(String[] args) {
    new Thread(new A(lock1, lock2), "A").start();
    new Thread(new B(lock1, lock2), "B").start();
  }
}

class A implements Runnable {

  // Keep references on resource
  private final Lock lock1;
  private final Lock lock2;

  public A(Lock lock1, Lock lock2) {
    this.lock1 = lock1;
    this.lock2 = lock2;
  }

  @Override
  public void run() {
    System.out.println("Instance of class A run()");

    while (true) {
      try {
        // System.out.println("Instance of class A acquiring lock1");
        // Attempt to acquire the lock with a timeout of 50 milliseconds
        // the timeout ensures that the attempts will 
        // be stopped in 50 milliseconds

        // Attention!!! It is the first part that 
        // demonstrates the Live lock behavior
        lock1.tryLock(50, TimeUnit.MILLISECONDS);
        System.out.println("Instance of class A acquired lock1");

        // Sleep symbolizes some work that the thread should be doing
        System.out.println("Instance of class A doing some work");
        Thread.sleep(50);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      //System.out.println("Instance of class A acquiring lock2");

      if (lock2.tryLock()) {
        System.out.println("Instance of class A acquired lock2");
      } else {
        // Attention!!! It is the second part that 
        // demonstrates the Live lock behavior
        System.out.println("Instance of class A cannot " 
          + "acquire lock2, releasing lock1.");
        lock1.unlock();
        // Continue to the next iteration of the loop
        continue;
      }

      // Never happens
      System.out.println("Instance of class A doing "
        + "some work, with lock1 and lock2");
      // breaks the loop
      break;
    }

    // Never happens
    // Unlock both locks
    lock2.unlock();
    lock1.unlock();
  }
}

class B implements Runnable {
  // Keep references on resource
  private final Lock lock1;
  private final Lock lock2;

  public B(Lock lock1, Lock lock2) {
    this.lock1 = lock1;
    this.lock2 = lock2;
  }

  @Override
  public void run() {
    while (true) {
      try {
        // System.out.println("Instance of class B acquiring lock2");
        // Attempt to acquire the lock with a timeout of 50 milliseconds
        // the timeout ensures that the attempts 
        // will be stopped in 50 milliseconds

        // Attention!!! It is the first part that 
        // demonstrates the Live lock behavior
        lock2.tryLock(50, TimeUnit.MILLISECONDS);
        System.out.println("Instance of class B acquired lock2 ");

        // Sleep symbolizes some work that the thread should be doing
        System.out.println("Instance of class B doing some work");
        Thread.sleep(50);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      // System.out.println("Instance of class A acquiring lock1");

      if (lock1.tryLock()) {
        System.out.println("Instance of class B acquired lock1");
      } else {
        // Attention!!! It is the second part that 
        // demonstrates the Live lock behavior
        System.out.println("Instance of class B cannot "
          + "acquire lock1, releasing lock2.");
        lock2.unlock();

        // Continue to the next iteration of the loop
        continue;
      }

      // Never happens
      System.out.println("Instance of class B doing "
        + "some work, with lock2 and lock1");
      // breaks the loop
      break;
    }
    // Never happens
    // Unlock both locks
    lock1.unlock();
    lock2.unlock();
  }
}

Deadlock vs Livelock

The main difference between a deadlock and a livelock is that in a deadlock, the execution is blocked, whereas in a livelock, the execution repeats the same loop indefinitely.

How to Debug and Prevent a Livelock

A livelock has all the same debugging and prevention techniques as a deadlock. However, there are a couple of exceptions:

  • A livelock is much harder to identify. All the system components seem to be working. Even automated monitoring systems have trouble identifying the behavior. 
  • If detected, it is a bit easier to troubleshoot since the system will produce logs, and there is a possibility to dump the system state at different stages and it will provide extra information.

Summary: What is a Livelock

  • A livelock is very similar to a deadlock
  • A livelock enters into an infinite loop of execution instead of having its execution blocked as it is in a deadlock
  • The majority of preventions and debugging techniques for livelocks are the same as for deadlocks
  • Livelocks are harder to identify than deadlocks
  • Debugging a livelock can be done by looking at the logs and dumping the system state