Wednesday, August 10, 2016

ReentrantLock usage and fix DeadLock

Test code:
Two MicroBlogNode to update themselves and treat each other as backup, then call confirm on the backup.

public class UpdateTest{
 public static void main(String[] args){
  Update.Builder ub = new Update.Builder();
  Author myAuthor = new Author();
  final Update u1 = ub.author(myAuthor).updateText("Hello1").build();
  final Update u2 = ub.author(myAuthor).updateText("Hello2").build();
  
  final MicroBlogNode mbn1 = new MicroBlogNode("11");
  final MicroBlogNode mbn2 = new MicroBlogNode("22");
  new Thread(){
   @Override
   public void run(){
    mbn1.propagateUpdate(u1, mbn2);
   }
  }.start();
  new Thread(){
   @Override
   public void run(){
    mbn2.propagateUpdate(u2, mbn1);
   }
  }.start();
 }
}

Look at a deadlock first, without ReentrantLock:

public class MicroBlogNode{
 private final String ident;
 public MicroBlogNode(String ident_) {
  ident = ident_;
 }
 public String getIdent() {
  return ident;
 }
 public synchronized void propagateUpdate(Update upd_, MicroBlogNode backup_) {
  System.out.println(ident + ": recvd: " + upd_.getUpdateText()
    + " ; backup: " + backup_.getIdent());
  backup_.confirmUpdate(this, upd_);
 }
 public synchronized void confirmUpdate(MicroBlogNode other_, Update update_) {
  System.out.println(ident + ": recvd confirm: "
    + update_.getUpdateText() + " from " + other_.getIdent());
 }
}
When Thread A in propagateUpdate and Thread B also in propagateUpdate, both of them are holding their own lock and waiting for each other to release the lock. This will cause deadlock.

Then we use ReentrantLock instead of synchronized, but this one is still deadlock in the same way.

public class MicroBlogNode1{
 private final String ident;
 private final Lock lock = new ReentrantLock();
 public MicroBlogNode1(String ident_) {
  ident = ident_;
 }
 public String getIdent() {
  return ident;
 }
 public void propagateUpdate(Update upd_, MicroBlogNode1 backup_) {
  lock.lock();
  try {
   System.out.println(ident + ": recvd: " + upd_.getUpdateText()
     + " ; backup: " + backup_.getIdent());
   backup_.confirmUpdate(this, upd_);
  }finally{
   lock.unlock();
  }
 }
 public void confirmUpdate(MicroBlogNode1 other_, Update update_) {
  lock.lock();
  try {
   System.out.println(ident + ": recvd confirm: "
    + update_.getUpdateText() + " from " + other_.getIdent());
  }finally{
   lock.unlock();
  }
 }
}

Trying to fix the deadlock, we can use tryLock with a timeout, and retry until we get the lock.
But this is still will cause  live busy DeadLock, because there will be a time Thread A and B keeping their own locks and retrying to acquire each other's.

public class MicroBlogNode2 {
 private final String ident;
 private final Lock lock = new ReentrantLock();
 public MicroBlogNode2(String ident_) {
  ident = ident_;
 } 
 private String getIdent() {
  return ident;
 }
 public void propagateUpdate(Update upd_, MicroBlogNode2 backup_) {
  boolean acquired = false;
  while (!acquired) {
   try {
    int wait = (int) (Math.random() * 10);
    acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);
    if (acquired) {
     System.out.println(ident + ": recvd: "
       + upd_.getUpdateText() + " ; backup: "
       + backup_.getIdent());
     backup_.confirmUpdate(this, upd_);
    } else {
     Thread.sleep(wait);
    }
   } catch (InterruptedException e) {
   } finally {
     // Attempts to lock other thread b Try and lock, with random
     // timeout Confirm on other thread
    if (acquired)
     lock.unlock();
   }
  }
 }
 public void confirmUpdate(MicroBlogNode2 other_, Update upd_) {
  lock.lock();// Attempts to lock other thread
  try {
   System.out.println(ident + ": recvd confirm: "
     + upd_.getUpdateText() + " from " + other_.getIdent());
  } finally {
   lock.unlock();
  }
 }
}

To really fix the DeadLock, the Thread need to release the lock when failed to acquire other's lock.
public class MicroBlogNode3 {
 private final String ident;
 private final Lock lock = new ReentrantLock();
 public MicroBlogNode3(String ident_) {
  ident = ident_;
 }
 public String getIdent() {
  return ident;
 }
 public void propagateUpdate(Update upd_, MicroBlogNode3 backup_) {
  boolean acquired = false;
  boolean done = false;
  while (!done) {
   int wait = (int) (Math.random() * 10);
   try {
    acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);
    if (acquired) {
     System.out.println(ident + ": recvd: "
       + upd_.getUpdateText() + " ; backup: "
       + backup_.getIdent());
     done = backup_.tryConfirmUpdate(this, upd_); //Examine return from tryConfirmUpdate()
    }
   } catch (InterruptedException e) {
   } finally {
    if (acquired)
     lock.unlock();
   }
   if (!done)
    try {
     Thread.sleep(wait);
    } catch (InterruptedException e) {
    }
  }
 }
 public boolean tryConfirmUpdate(MicroBlogNode3 other_, Update upd_) {
  boolean acquired = false;
  try {
   long startTime = System.currentTimeMillis();
   int wait = (int) (Math.random() * 10);
   acquired = lock.tryLock(wait, TimeUnit.MILLISECONDS);
   if (acquired) {
    long elapsed = System.currentTimeMillis() - startTime;
    System.out.println(ident + ": recvd confirm: "
      + upd_.getUpdateText() + " from " + other_.getIdent()
      + " - took " + elapsed + " millis");
    return true;
   }
  } catch (InterruptedException e) {
  } finally {
   if (acquired)
    lock.unlock();
  }
  return false;
 }
}





















1 comment: