In Java, each Object
provides the ability for a Thread to synchronize
, or lock, on it. When a method is synchronized, the method uses its object instance as the lock. In your example, the methods bow
and bowBack
are both synchronized
, and both are in the same class Friend
. This means that any Thread executing these methods will synchronize on a Friend
instance as its lock.
A sequence of events which will cause a deadlock is:
- The first Thread started calls
alphonse.bow(gaston)
, which is synchronized
on the alphonse
Friend
object. This means the Thread must acquire the lock from this object.
- The second Thread started calls
gaston.bow(alphonse)
, which is synchronized
on the gaston
Friend
object. This means the Thread must acquire the lock from this object.
- The first thread started now calls
bowback
and waits for the lock on gaston
to be released.
- The second thread started now calls
bowback
and waits for the lock on alphonse
to be released.
To show the sequence of events in much more detail:
main()
begins to execute in the main Therad (call it Thread #1), creating two Friend
instances. So far, so good.
- The main Thread starts its first new Thread (call it Thread #2) with the code
new Thread(new Runnable() { ...
. Thread #2 calls alphonse.bow(gaston)
, which is synchronized
on the alphonse
Friend
object. Thread #2 thus acquires the "lock" for the alphonse
object and enters the bow
method.
- A time slice occurs here and the original Thread gets a chance to do more processing.
- The main Thread starts a second new Thread (call it Thread #3), just like the first one. Thread #3 calls
gaston.bow(alphonse)
, which is synchronized on the gaston
Friend
object. Since no-one has yet acquired the "lock" for the gaston
object instance, Thread #3 successfully acquires this lock and enters the bow
method.
- A time slice occurs here and Thread #2 gets a chance to do more processing.
- Thread #2 now calls
bower.bowBack(this);
with bower
being a reference to the instance for gaston
. This is the logical equivalent of a call of gaston.bowBack(alphonse)
. Thus, this method is synchronized
on the gaston
instance. The lock for this object has already been acquired and is held by another Thread (Thread #3). Thus, Thread #2 has to wait for the lock on gaston
to be released. The Thread is put into a waiting state, allowing Thread #3 to execute further.
- Thread #3 now calls
bowback
, which in this instance is logically the same as the call alphonse.bowBack(gaston)
. To do this, it needs to acquire the lock for the alphonse
instance, but this lock is held by Thread #2. This Thread is now put into a waiting state.
And you are now in a position where neither Thread can execute. Both Thread #2 and Thread #3 are waiting for a lock to be released. But neither lock can be released without a Thread making progress. But neither thread can make progress without a lock being released.
Thus: Deadlock!
Deadlocks very often depend on a specific sequence of events occurring, which can make then difficult to debug since they can be difficult to reproduce.