peter59 peter59 - 1 month ago 18
Java Question

Thread deadlock avoidance

I want to know if it's authorized to avoid Thread deadlocks by making the threads not starting at the same time? Is there an other way to avoid the deadlocks in the following code?

Thanks in advance!

public class ThreadDeadlocks {

public static Object Lock1 = new Object();
public static Object Lock2 = new Object();

public static void main(String args[]) {

ThreadDemo1 t1 = new ThreadDemo1();
ThreadDemo2 t2 = new ThreadDemo2();

t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
t2.start();
}

private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}

private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 2...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}

Answer

No, starting threads at different times is not a way to avoid deadlocks - in fact, what you'd be trying with different start times is a heuristic to serialize their critical sections. ++ see why at the and of this answer

[Edited with a solution]

Is there an other way to avoid the deadlocks in the following code?

The simplest way is to acquire the locks in the same order on both threads

synchronized(Lock1) {
   // do some work
  synchronized(Lock2) {
     // do some other work and commit (make changes visible)
  }
}

If the logic of your code dictates you can't do that, then use java.util.concurrent.locks classes. For example

ReentrantLock Lock1=new ReentrantLock();
ReentrantLock Lock2=new ReentrantLock();

private static class ThreadDemo1 extends Thread {
    public void run() {
         while(true) {
            Lock1.lock(); // will block until available
            System.out.println("Thread 1: Holding lock 1...");
            try {
                // Do some preliminary work here, but do not "commit" yet
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 1: Waiting for lock 2...");
            if(!Lock2.tryLock(30, TimeUnit.MILLISECOND)) {
               System.out.println("Thread 1: not getting a hold on lock 2...");

               // altruistic behaviour: if I can't do it, let others 
               // do their work with Lock1, I'll try later
               System.out.println("Thread 1: release lock 1 and wait a bit");
               Lock1.unlock();
               Thread.sleep(30);
               System.out.println("Thread 1: Discarding the work done before, will retry getting lock 1");

            }
            else {
               System.out.println("Thread 1: got a hold on lock 2...");
               break;
            }
        }
        // if we got here, we know we are holding both locks
        System.out.println("Thread 1: both locks available, complete the work");
        // work...
        Lock2.unlock(); // release the locks in the reverse... 
        Lock1.unlock(); // ... order of acquisition

    }
}

// do the same for the second thread

++ To demonstrate why delays in starting the threads at different times is not a foolproof solution, think if you can afford to delay one of the threads by 10 seconds in the example below. Then think what will you do if you don't actually know how long to wait.

private static class ThreadDemo1 extends Thread {
    public void run() {
        synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try {
                // modelling a workload here: 
                // can take anywhere up to 10 seconds
                Thread.sleep((long)(Math.random()*10000));
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 1: Waiting for lock 2...");
            synchronized (Lock2) {
                System.out.println("Thread 1: Holding lock 1 & 2...");
            }
        }
    }
}

private static class ThreadDemo2 extends Thread {
    public void run() {
        synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            try {
                // modelling a workload here: 
                // can take anywhere up to 10 seconds
                Thread.sleep((long)(Math.random()*10000));
            } catch (InterruptedException e) {
            }
            System.out.println("Thread 2: Waiting for lock 1...");
            synchronized (Lock1) {
                System.out.println("Thread 2: Holding lock 1 & 2...");
            }
        }
    }
}
Comments