|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 7
field_1=0, field2=0
But it could just as easily print:
field_1=0, field2=1
or
field_1=1, field2=1
There's just no telling. (In the first and last cases, the thread would have been outside the println statement when modify() was called. In the second example, the thread would have been halfway through evaluating the arguments to println(), having fetched field_1, but not field_2. It prints the unmodified field_1 and the modified field_2.
The main issue here is that there is no simple solution to this problem. The modify() method is indeed synchronized in the earlier example, but run() can't be. Were it synchronized, you'd enter the monitor (and lock the object), when you started up the thread. Thereafter,
any other thread that called any synchronized method on the object (such as modify()) would block until the monitor was released. Since run() doesn't return (as is often the case), the release will never happen, and the thread will act like a black hole, sucking
up any other thread that calls any of its synchronized methods. In the current example, the main thread would be suspended,
and the program would hang. So just using the synchronized keyword in a naive way gets us nowhere.
Synchronizing run() is a good example of a simple deadlock scenario, where a thread is blocked forever, waiting for something to happen that can't. Let's look at a few examples that
are more realistic than this.
The most common deadlock scenario occurs when two threads are both waiting for each other to do something. The following (admittedly contrived) code snippet makes what's going on painfully obvious:
class Flintstone
{
int field_1; private Object lock_1 = new int[1];
int field_2; private Object lock_2 = new int[1];
public void fred( int value )
{ synchronized( lock_1 )
{ synchronized( lock_2 )
{
field_1 = 0;
field_2 = 0;
}
}
}
public void barney( int value )
{ synchronized( lock_2 )
{ synchronized( lock_1 )
{
field_1 = 0;
field_2 = 0;
}
}
}
}
Now, imagine a scenario whereby one thread (call it Wilma) calls fred(), passes through the synchronization of lock_1, and is then preempted, allowing another thread (call it Betty) to execute. Betty calls barney(), acquires lock_2, and tries to acquire lock_1, but can't because Wilma has it. Betty is now blocked, waiting for lock_1 to become available, so Wilma wakes up and tries to acquire lock_2 but can't because Betty has it. Wilma and Betty are now deadlocked. Neither one can ever execute.
(Note that lock_1 and lock_2 have to be one-element arrays rather than simple ints, because only objects have monitors in Java; the argument to synchronized must be an object. An array is a first-class object in Java; a primitive-type such as int is not. Consequently, you can synchronize on it. Moreover, a one-element array is efficient to bring into existence compared
to a more elaborate object (like an Integer) since it's both small and does not require a constructor call. Also, note that I can keep the reference to the lock as a
simple Object reference, since I'll never access the array elements.)