4.4 Deadlock
Multithreaded programming requires a
programmer to take special care in several areas. For example, if
multiple threads can be changing the state of an object at the same
time, you typically must use synchronized methods
or the synchronized statement to ensure that only
one thread changes the object's state at a time. If
you do not, two threads could end up overwriting each
other's edits, leaving the object in an inconsistent
state.Unfortunately, using
synchronization can itself cause problems. Thread synchronization
involves acquiring an exclusive lock. Only the one thread that
currently holds the lock can execute the synchronized code. When a
program uses more than one lock, however, a situation known as
deadlock can arise. Deadlock occurs when two or more threads are all
waiting to acquire a lock that is currently held by one of the other
waiting threads. Because each thread is waiting to acquire a lock,
none ever releases the lock or locks it already holds, which means
that none of the waiting threads ever acquires the lock it is waiting
for. The situation is a total impasse; all the threads involved come
to a halt, and the program can't continue.Example 4-4 is a simple program that creates a deadlock
situation in which two threads attempt to acquire locks on two
different resources. It is pretty easy to see how deadlock can arise
in this simple program. It might not be as clear, however, if there
were synchronized methods involved, instead of a
simple symmetrical set of synchronized statements.
More complicated situations also arise with multiple threads and
multiple resources. In general, the problem of deadlock is a deep and
nasty one. One good technique for preventing it, however, is for all
threads always to acquire all the locks they need in the same order.
Example 4-4. Deadlock.java
package je3.thread;
/**
* This is a demonstration of how NOT to write multi-threaded programs.
* It is a program that purposely causes deadlock between two
threads that
* are both trying to acquire locks for the same two resources.
* To avoid this sort of deadlock when locking multiple resources,
all threads
* should always acquire their locks in the same order.
**/
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, just like the first thread.
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( );
}
}