|
|
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 4 of 7
Some critical code sections occupy small portions of their enclosing methods. To guard multiple thread access to such critical
code sections, you use the synchronized statement. That statement has the following syntax:
'synchronized' '(' objectidentifier ')'
'{'
// Critical code section
'}'
The synchronized statement begins with keyword synchronized and continues with an objectidentifier, which appears between a pair of round brackets. The objectidentifier references an object whose lock associates with the monitor that the synchronized statement represents. Finally, the Java statements' critical code section appears between a pair of brace characters. How
do you interpret the synchronized statement? Consider the following code fragment:
synchronized ("sync object")
{
// Access shared variables and other shared resources
}
From a source code perspective, a thread attempts to enter the critical code section that the synchronized statement guards. Internally, the JVM checks if some other thread holds the lock associated with the "sync object" object. (Yes, "sync object" is an object. You will understand why in a future article.) If no other thread holds the lock, the JVM gives the lock to
the requesting thread and allows that thread to enter the critical code section between the brace characters. However, if
some other thread holds the lock, the JVM forces the requesting thread to wait in a private waiting area until the thread
currently within the critical code section finishes executing the final statement and transitions past the final brace character.
You can use the synchronized statement to eliminate NeedForSynchronizationDemo's race condition. To see how, examine Listing 2:
// SynchronizationDemo1.java
class SynchronizationDemo1
{
public static void main (String [] args)
{
FinTrans ft = new FinTrans ();
TransThread tt1 = new TransThread (ft, "Deposit Thread");
TransThread tt2 = new TransThread (ft, "Withdrawal Thread");
tt1.start ();
tt2.start ();
}
}
class FinTrans
{
public static String transName;
public static double amount;
}
class TransThread extends Thread
{
private FinTrans ft;
TransThread (FinTrans ft, String name)
{
super (name); // Save thread's name
this.ft = ft; // Save reference to financial transaction object
}
public void run ()
{
for (int i = 0; i < 100; i++)
{
if (getName ().equals ("Deposit Thread"))
{
synchronized (ft)
{
ft.transName = "Deposit";
try
{
Thread.sleep ((int) (Math.random () * 1000));
}
catch (InterruptedException e)
{
}
ft.amount = 2000.0;
System.out.println (ft.transName + " " + ft.amount);
}
}
else
{
synchronized (ft)
{
ft.transName = "Withdrawal";
try
{
Thread.sleep ((int) (Math.random () * 1000));
}
catch (InterruptedException e)
{
}
ft.amount = 250.0;
System.out.println (ft.transName + " " + ft.amount);
}
}
}
}
}
Look carefully at SynchronizationDemo1; the run() method contains two critical code sections sandwiched between synchronized (ft) { and }. Each of the deposit and withdrawal threads must acquire the lock that associates with the FinTrans object that ft references before either thread can enter its critical code section. If, for example, the deposit thread is in its critical
code section and the withdrawal thread wants to enter its own critical code section, the withdrawal thread attempts to acquire
the lock. Because the deposit thread holds that lock while it executes within its critical code section, the JVM forces the
withdrawal thread to wait until the deposit thread executes that critical code section and releases the lock. (When execution
leaves the critical code section, the lock releases automatically.)