Wednesday, December 5, 2018

Livelock in Java Multi-Threading

Livelock in Java multi-threading is a situation where two or more threads are acting on a response to an action of each other and not able to make any progress because of that.

How livelock is different from deadlock in Java multi-threading is that in case of deadlock threads get blocked whereas in case of livelock threads are active but busy responding to each other thus not making any progress.

Livelock Java example

Let’s see example of livelock in Java multi-threading to understand it. A classic example to explain livelock is a multi-threaded application to deposit and withdraw money from accounts with a provision to rollback the transaction if transaction doesn’t go through.

import java.util.concurrent.locks.ReentrantLock;

public class LivelockDemo {

    public static void main(String[] args) {
        Account acct1 = new Account(101, 5000);
        Account acct2 = new Account(102, 7000);
        // Creating two threads
        Thread thread1 = new Thread(new Operation(acct1, acct2, 100));
        Thread thread2 = new Thread(new Operation(acct2, acct1, 100));

        thread1.start();
        thread2.start();
    }
}

class Account{
    int acctNum;
    int balance;
    ReentrantLock lock = new ReentrantLock();
    Account(int acctNum, int balance){
        this.acctNum = acctNum;
        this.balance = balance;
    }
    
    /**
     * Method for depositing amount
     * @param amount
     * @return
     */
    public boolean deposit(int amount){
        System.out.println("In deposit method");
        if(this.lock.tryLock()){
            try {
                // Simulating some delay
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("deposit in " + this.acctNum + " for " 
              + Thread.currentThread().getName());
            this.balance = balance + amount;
            return true;
        }
        return false;
    }
    
    /**
     * Method for withdrawing amount
     * @param amount
     * @return
     */
    public boolean withdraw(int amount){
        System.out.println("In withdraw method");
        if(this.lock.tryLock()){
            try {
                // Simulating some delay
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("Withdrawn from " + this.acctNum + " for " 
              + Thread.currentThread().getName());
            this.balance = balance - amount;
            return true;
        }
        return false;
    }
    
    public boolean transact(Account targetAcct, int amount){
        //System.out.println("In transact method " + targetAcct);
        boolean flag = false;
        // If you can withdraw from the source account and
        // deposit it into target account then only return true        
        if(this.withdraw(amount) && targetAcct.deposit(amount)){
                flag = true;
        }else{
            // Rollback and deposit the withdrawn amount back in source account    
            System.out.println("Failed to deposit in " + targetAcct.acctNum + 
              " depositing back in account " + this.acctNum);
            this.deposit(amount);
            
        }
        return flag;
    }
}

class Operation implements Runnable{
    Account sourceAccount;
    Account targetAccount;
    int amount;
    Operation(Account sourceAccount, Account targetAccount, int amount){
        this.sourceAccount = sourceAccount;
        this.targetAccount = targetAccount;
        this.amount = amount;
    }

    @Override
    public void run() {
        
        sourceAccount.transact(targetAccount, amount);
    }
    
}

Output

In withdraw method
In withdraw method
Withdrawn from 102 for Thread-1
In deposit method
Withdrawn from 101 for Thread-0
In deposit method
Failed to deposit in 101 depositing back in account 102
In deposit method
Failed to deposit in 102 depositing back in account 101
In deposit method
deposit in 102 for Thread-1
deposit in 101 for Thread-0

Here you can see that you have two threads where one is withdrawing amount from Account 101 and depositing it in 102 and the other thread is withdrawing amount from Account 102 and depositing it in 101. Threads are failing to complete the transaction because of not able to get the lock and rolling back the transaction.

In the above example transaction is not run in the loop so the threads make just one attempt, you can change the run method as follows and see how both threads keep trying and rolling back the transactions without making any real progress.

public void run() {
 while(!sourceAccount.transact(targetAccount, amount)){
  continue;
 } 
}

If you run your code with these changes in run() method you will have to terminate your program to come out of the loop.

Output

In withdraw method
In withdraw method
Withdrawn from 101 for Thread-0
Withdrawn from 102 for Thread-1
In deposit method
In deposit method
Failed to deposit in 101 depositing back in account 102
In deposit method
Failed to deposit in 102 depositing back in account 101
In deposit method
deposit in 102 for Thread-1
In withdraw method
deposit in 101 for Thread-0
In withdraw method
Withdrawn from 101 for Thread-0
In deposit method
Withdrawn from 102 for Thread-1
In deposit method
Failed to deposit in 102 depositing back in account 101
In deposit method
Failed to deposit in 101 depositing back in account 102
In deposit method
deposit in 102 for Thread-1
In withdraw method
deposit in 101 for Thread-0
In withdraw method
Withdrawn from 102 for Thread-1
In deposit method
Withdrawn from 101 for Thread-0
In deposit method
Failed to deposit in 101 depositing back in account 102
In deposit method
Failed to deposit in 102 depositing back in account 101
In deposit method
deposit in 101 for Thread-0
In withdraw method
deposit in 102 for Thread-1
In withdraw method
Withdrawn from 101 for Thread-0
In deposit method
Withdrawn from 102 for Thread-1
In deposit method

That's all for this topic Livelock in Java Multi-Threading. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Thread Starvation in Java Multi-Threading
  2. Why wait(), notify() and notifyAll() methods are in Object class
  3. Race condition in Java multi-threading
  4. ReentrantLock in Java concurrency
  5. Java Multi-threading interview questions

You may also like-

  1. Lock Striping in Java Concurrency
  2. AtomicInteger in Java Concurrency
  3. Executor and ExecutorService in Java concurrency
  4. How HashSet works internally in Java
  5. New Date and Time API in Java 8
  6. Heap Memory Allocation in Java
  7. Difference Between Checked & Unchecked Exception in Java
  8. String charAt() and substring() methods in Java