Programming Tutorials Browser Tutorials Articles Struts Tutorials Hibernate Tutorials

  Tutorial: Achieve strong performance with threads, Part 2

Achieve strong performance with threads, Part 2

Tutorial Details:

Achieve strong performance with threads, Part 2
Achieve strong performance with threads, Part 2
By: By Jeff Friesen
Use synchronization to serialize thread access to critical code sections
s creating multithreaded Java programs hard? With the information gleaned from Part 1 of Java 101 's thread series only, you might answer no. After all, last month I showed you how easy it is to create thread objects, start threads that associate with those objects by calling Thread 's start() method, and perform simple thread operations by calling other Thread methods, such as the three overloaded join() methods. Yet many developers face difficulty when developing properly behaving multithreaded programs. All too often, their programs function erratically or produce erroneous values. For example, a multithreaded program might store incorrect employee details, such as name and address, in a database. The name might belong to one employee, whereas the address belongs to another. What causes that strange behavior? The lack of synchronization: the act of serializing, or ordering one at a time, thread access to those code sequences that let multiple threads manipulate class and instance field variables, and other shared resources. I call those code sequences critical code sections.
Note
Unlike class and instance field variables, threads cannot share local variables and parameters. The reason: Local variables and parameters allocate on a thread's method-call stack. As a result, each thread receives its own copy of those variables. In contrast, threads can share class fields and instance fields because those variables do not allocate on a thread's method-call stack. Instead, they allocate in shared heap memory?as part of classes (class fields) or objects (instance fields).
This article, the second in a four-part series that explores threads, teaches you how to use synchronization to serialize thread access to critical code sections. I begin with an example that illustrates why some multithreaded programs must use synchronization. I next explore Java's synchronization mechanism in terms of monitors and locks, and the synchronized keyword. Because incorrectly using the synchronization mechanism negates its benefits, I conclude by investigating two problems that result from such misuse.
Read the whole series on thread programming:
Part 1: Introducing threads, the Thread class, and runnables
Part 2: Use synchronization to serialize thread access to critical code sections
Part 3: Learn about thread scheduling, the wait/notify mechanism, and thread interruption
Part 4: Discover thread groups, volatility, thread-local variables, timers, and the ThreadDeath class
The need for synchronization
Why do we need synchronization? For an answer, consider this example: You write a Java program that uses a pair of threads to simulate withdrawal/deposit of financial transactions. In that program, one thread performs deposits while the other performs withdrawals. Each thread manipulates a pair of shared variables, class and instance field variables, that identifies the financial transaction's name and amount. For a correct financial transaction, each thread must finish assigning values to the name and amount variables (and print those values, to simulate saving the transaction) before the other thread starts assigning values to name and amount (and also printing those values). After some work, you end up with source code that resembles Listing 1:
Listing 1. NeedForSynchronizationDemo.java
// NeedForSynchronizationDemo.java
class NeedForSynchronizationDemo
{
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"))
{
// Start of deposit thread's critical code section
ft.transName = "Deposit";
try
{
Thread.sleep ((int) (Math.random () * 1000));
}
catch (InterruptedException e)
{
}
ft.amount = 2000.0;
System.out.println (ft.transName + " " + ft.amount);
// End of deposit thread's critical code section
}
else
{
// Start of withdrawal thread's critical code section
ft.transName = "Withdrawal";
try
{
Thread.sleep ((int) (Math.random () * 1000));
}
catch (InterruptedException e)
{
}
ft.amount = 250.0;
System.out.println (ft.transName + " " + ft.amount);
// End of withdrawal thread's critical code section
}
}
}
}
NeedForSynchronizationDemo 's source code has two critical code sections: one accessible to the deposit thread, and the other accessible to the withdrawal thread. Within the deposit thread's critical code section, that thread assigns the Deposit String object's reference to shared variable transName and assigns 2000.0 to shared variable amount . Similarly, within the withdrawal thread's critical code section, that thread assigns the Withdrawal String object's reference to transName and assigns 250.0 to amount . Following each thread's assignments, those variables' contents print. When you run NeedForSynchronizationDemo , you might expect output similar to a list of interspersed Withdrawal 250.0 and Deposit 2000.0 lines. Instead, you receive output resembling the following:
Withdrawal 250.0
Withdrawal 2000.0
Deposit 2000.0
Deposit 2000.0
Deposit 250.0
The program definitely has a problem. The withdrawal thread should not be simulating $2,000 withdrawals, and the deposit thread should not be simulating $250 deposits. Each thread produces inconsistent output. What causes those inconsistencies? Consider the following:
On a single-processor machine, threads share the processor. As a result, one thread can only execute for a certain time period. At that time, the JVM/operating system pauses that thread's execution and allows another thread to execute?a manifestation of thread scheduling, a topic I discuss in Part 3. On a multiprocessor machine, depending on the number of threads and processors, each thread can have its own processor.
On a single-processor machine, a thread's execution period might not last long enough for that thread to finish executing its critical code section before another thread begins executing its own critical code section. On a multiprocessor machine, threads can simultaneously execute code in their critical code sections. However, they might enter their critical code sections at different times.
On either single-processor or multiprocessor machines, the following scenario can occur: Thread A assigns a value to shared variable X in its critical code section and decides to perform an input/output operation that requires 100 milliseconds. Thread B then enters its critical code section, assigns a different value to X, performs a 50-millisecond input/output operation, and assigns values to shared variables Y and Z. Thread A's input/output operation completes, and that thread assigns its own values to Y and Z. Because X contains a B-assigned value, whereas Y and Z contain A-assigned values, an inconsistency results.
How does an inconsistency arise in NeedForSynchronizationDemo ? Suppose the deposit thread executes ft.transName = "Deposit"; and then calls Thread.sleep() . At that point, the deposit thread surrenders control of the processor for the time period it must sleep, and the withdrawal thread executes. Assume the deposit thread sleeps for 500 milliseconds (a randomly selected value, thanks to Math.random() , from the inclusive range 0 through 999 milliseconds; I explore Math and its random() method in a future article). During the deposit thread's sleep time, the withdrawal thread executes ft.transName = "Withdrawal"; , sleeps for 50 milliseconds (the withdrawal thread's randomly selected sleep value), awakes, executes ft.amount = 250.0; , and executes System.out.println (ft.transName + " " + ft.amount); ?all before the deposit thread awakes. As a result, the withdrawal thread prints Withdrawal 250.0 , which is correct. When the deposit thread awakes, it executes ft.amount = 2000.0; , followed by System.out.println (ft.transName + " " + ft.amount); . This time, Withdrawal 2000.0 prints, which is not correct. Although the deposit thread previously assigned the "Deposit" 's reference to transName , that reference subsequently disappeared when the withdrawal thread assigned the "Withdrawal" 's reference to that shared variable. When the deposit thread awoke, it failed to restore the correct reference to transName , but continued its execution by assigning 2000.0 to amount . Although neither variable has an invalid value, the combined values of both variables represent an inconsistency. In this case, their values represent an attempt to withdraw $2,000.
Long ago, computer scientists invented a term to describe the combined behaviors of multiple threads that lead to inconsistencies. That term is race condition ?the act of each thread racing to complete its critical code section before some other thread enters that same critical code section. As NeedForSynchronizationDemo demonstrates, threads' execution orders are unpredictable. There is no guarantee that a thread can complete its critical code section before some other thread enters that section. Hence, we have a race condition, which causes inconsistencies. To prevent race conditions, each thread must complete its critical code section before another thread enters either the same critical code section or another related critical code section that manipulates the same shared variables or resources. With no means of serializing access?that is, allowing access to on


 

Read Tutorial at: Click here to view the tutorial

Rate Tutorial:
Achieve strong performance with threads, Part 2

View Tutorial:
Achieve strong performance with threads, Part 2

Related Tutorials:

Programming Java threads in the real world, Part 1 - JavaWorld - September 1998
Programming Java threads in the real world, Part 1 - JavaWorld - September 1998
 
Programming Java threads in the real world, Part 2 - JavaWorld - October 1998
Programming Java threads in the real world, Part 2 - JavaWorld - October 1998
 
Programming Java threads in the real world, Part 5 - JavaWorld - February 1999
Programming Java threads in the real world, Part 5 - JavaWorld - February 1999
 
The Java HotSpot Performance Engine is set to break new records - JavaWorld
The Java HotSpot Performance Engine is set to break new records - JavaWorld
 
A promise of easier embedded-systems networking - JavaWorld November 1999
A promise of easier embedded-systems networking - JavaWorld November 1999
 
Smart object-management saves the day - JavaWorld November 1999
Smart object-management saves the day - JavaWorld November 1999
 
Java performance programming, Part 3: Managing collections - JavaWorld February 2000
Java performance programming, Part 3: Managing collections - JavaWorld February 2000
 
Activatable Jini services, Part 2: Patterns of use - JavaWorld October 2000
Activatable Jini services, Part 2: Patterns of use - JavaWorld October 2000
 
Construct secure networked applications with certificates, Part 1 - JavaWorld January 2001
Construct secure networked applications with certificates, Part 1 - JavaWorld January 2001
 
Performance books put to the test - JavaWorld March 2001
Performance books put to the test - JavaWorld March 2001
 
J2EE project dangers! - JavaWorld March 2001
J2EE project dangers! - JavaWorld March 2001
 
Avoid synchronization deadlocks
Avoid synchronization deadlocks
 
Can ThreadLocal solve the double-checked locking problem?
Can ThreadLocal solve the double-checked locking problem?
 
Achieve strong performance with threads, Part 1
Achieve strong performance with threads, Part 1
 
Study guide Achieve strong performance with threads Part 1
Study guide Achieve strong performance with threads Part 1
 
Achieve strong performance with threads, Part 2
Achieve strong performance with threads, Part 2
 
Get the inside track on J2EE architect certification
Get the inside track on J2EE architect certification
 
Very interesting
Very interesting
 
elegant turnaround
elegant turnaround
 
Fixing the Java Memory Model, Part 2
Writing concurrent code is hard to begin with; the language should not make it any harder. While the Java platform included support for threading from the outset, including a cross-platform memory model that was intended to provide \"Write Once, Run Anywh
 
Site navigation
 

 

Send your comments, Suggestions or Queries regarding this site at roseindia_net@yahoo.com.

Copyright © 2006. All rights reserved.