I would like to explain threading deadlocks to newbies. I have seen many examples for deadlocks in the past, some using code and some using illustrations (like the famous 4
Here's one simple deadlock in Java. We need two resources for demonstrating deadlock. In below example, one resource is class lock(via sync method) and the other one is an integer 'i'
public class DeadLock {
static int i;
static int k;
public static synchronized void m1(){
System.out.println(Thread.currentThread().getName()+" executing m1. Value of i="+i);
if(k>0){i++;}
while(i==0){
System.out.println(Thread.currentThread().getName()+" waiting in m1 for i to be > 0. Value of i="+i);
try { Thread.sleep(10000);} catch (InterruptedException e) { e.printStackTrace(); }
}
}
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
public void run() {
m1();
}
};
Thread t2 = new Thread("t2") {
public void run() {
try { Thread.sleep(100);} catch (InterruptedException e) { e.printStackTrace(); }
k++;
m1();
}
};
t1.start();
t2.start();
}
}
Go for the simplist possible scenario in which deadlock can occur when introducting the concept to your students. This would involve a minimum of two threads and a minimum of two resources (I think). The goal being to engineer a scenario in which the first thread has a lock on resource one, and is waiting for the lock on resource two to be released, whilst at the same time thread two holds a lock on resource two, and is waiting for the lock on resource one to be released.
It doesn't really matter what the underlying resources are; for simplicities sake, you could just make them a pair of files that both threads are able to write to.
EDIT: This assumes no inter-process communication other than the locks held.
Let me explain more clearly using an example having more than 2 threads.
Let us say you have n threads each holding locks L1, L2, ..., Ln respectively. Now let's say, starting from thread 1, each thread tries to acquire its neighbour thread's lock. So, thread 1 gets blocked for trying to acquire L2 (as L2 is owned by thread 2), thread 2 gets blocked for L3 and so on. The thread n gets blocked for L1. This is now a deadlock as no thread is able to execute.
class ImportantWork{
synchronized void callAnother(){
}
synchronized void call(ImportantWork work) throws InterruptedException{
Thread.sleep(100);
work.callAnother();
}
}
class Task implements Runnable{
ImportantWork myWork, otherWork;
public void run(){
try {
myWork.call(otherWork);
} catch (InterruptedException e) {
}
}
}
class DeadlockTest{
public static void main(String args[]){
ImportantWork work1=new ImportantWork();
ImportantWork work2=new ImportantWork();
ImportantWork work3=new ImportantWork();
Task task1=new Task();
task1.myWork=work1;
task1.otherWork=work2;
Task task2=new Task();
task2.myWork=work2;
task2.otherWork=work3;
Task task3=new Task();
task3.myWork=work3;
task3.otherWork=work1;
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
}
}
In the above example, you can see that there are three threads holding Runnable
s task1, task2, and task3. Before the statement sleep(100)
the threads acquire the three work objects' locks when they enter the call()
method (due to the presence of synchronized
). But as soon as they try to callAnother()
on their neighbour thread's object, they are blocked, leading to a deadlock, because those objects' locks have already been taken.
I have created an ultra Simple Working DeadLock Example:-
package com.thread.deadlock;
public class ThreadDeadLockClient {
public static void main(String[] args) {
ThreadDeadLockObject1 threadDeadLockA = new ThreadDeadLockObject1("threadDeadLockA");
ThreadDeadLockObject2 threadDeadLockB = new ThreadDeadLockObject2("threadDeadLockB");
new Thread(new Runnable() {
@Override
public void run() {
threadDeadLockA.methodA(threadDeadLockB);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadDeadLockB.methodB(threadDeadLockA);
}
}).start();
}
}
package com.thread.deadlock;
public class ThreadDeadLockObject1 {
private String name;
ThreadDeadLockObject1(String name){
this.name = name;
}
public synchronized void methodA(ThreadDeadLockObject2 threadDeadLockObject2) {
System.out.println("In MethodA "+" Current Object--> "+this.getName()+" Object passed as parameter--> "+threadDeadLockObject2.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
threadDeadLockObject2.methodB(this);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.thread.deadlock;
public class ThreadDeadLockObject2 {
private String name;
ThreadDeadLockObject2(String name){
this.name = name;
}
public synchronized void methodB(ThreadDeadLockObject1 threadDeadLockObject1) {
System.out.println("In MethodB "+" Current Object--> "+this.getName()+" Object passed as parameter--> "+threadDeadLockObject1.getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
threadDeadLockObject1.methodA(this);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In the above example 2 threads are executing the synchronized methods of two different objects. Synchronized methodA is called by object threadDeadLockA and synchronized methodB is called by object threadDeadLockB. In methodA a reference of threadDeadLockB is passed and in methodB a reference of threadDeadLockA is passed. Now each thread tries to get lock on the another object. In methodA the thread who is holding a lock on threadDeadLockA is trying to get lock on object threadDeadLockB and similarly in methodB the thread who is holding a lock on threadDeadLockB is trying to get lock on threadDeadLockA. Thus both the threads will wait forever creating a deadlock.
I consider the Dining Philosophers problem to be one of the more simple examples in showing deadlocks though, since the 4 deadlock requirements can be easily illustrated by the drawing (especially the circular wait).
I consider real world examples to be much more confusing to the newbie, though I can't think of a good real world scenario off the top of my head right now (I'm relatively inexperienced with real-world concurrency).
One more simple deadlock example with two different resources and two thread waiting for each other to release resource. Directly from examples.oreilly.com/jenut/Deadlock.java
public class Deadlock {
public static void main(String[] args) {
// These are the two resource objects we'll try to get locks for
final Object resource1 = "resource1";
final Object resource2 = "resource2";
// Here's the first thread. It tries to lock resource1 then resource2
Thread t1 = new Thread() {
public void run() {
// Lock resource 1
synchronized(resource1) {
System.out.println("Thread 1: locked resource 1");
// Pause for a bit, simulating some file I/O or something.
// Basically, we just want to give the other thread a chance to
// run. Threads and deadlock are asynchronous things, but we're
// trying to force deadlock to happen here...
try { Thread.sleep(50); } catch (InterruptedException e) {}
// Now wait 'till we can get a lock on resource 2
synchronized(resource2) {
System.out.println("Thread 1: locked resource 2");
}
}
}
};
// Here's the second thread. It tries to lock resource2 then resource1
Thread t2 = new Thread() {
public void run() {
// This thread locks resource 2 right away
synchronized(resource2) {
System.out.println("Thread 2: locked resource 2");
// Then it pauses, for the same reason as the first thread does
try { Thread.sleep(50); } catch (InterruptedException e) {}
// Then it tries to lock resource1. But wait! Thread 1 locked
// resource1, and won't release it 'till it gets a lock on
// resource2. This thread holds the lock on resource2, and won't
// release it 'till it gets resource1. We're at an impasse. Neither
// thread can run, and the program freezes up.
synchronized(resource1) {
System.out.println("Thread 2: locked resource 1");
}
}
}
};
// Start the two threads. If all goes as planned, deadlock will occur,
// and the program will never exit.
t1.start();
t2.start();
}
}