Witscale Test Center

12.3 Access to shared data/code with synchronization


12.3 Access to shared data/code with synchronization


The great characteristic of threads is their ability to run in parallel. The same trait is also the cause of concern about threads. This is especially true when we have multiple threads all accessing the same instance of a class. The order of execution of thread may change the value of some variable or the outcome of that program in an unpredictable fashion. This peculiar characteristic of multi-threaded execution is called as race condition. Let us prove this with actual code. Listing 12.5 shows two classes defined in separate files. The UtilityBillManager is runnable class that pays the bill using the UtilityBill class. For simplicity of code, a fixed amount of 25 is assigned to the variable amountDue in UtilityBill.

 



 

The ThreadTester class tests it by creating and starting two threads thread1 and thread2 that are created on common runnable class as-

 

public class ThreadTester{

   public static void main(String[] args) {

UtilityBillManager runnable = new UtilityBillManager();

     Thread thread1 = new Thread(runnable);

     Thread thread2 = new Thread(runnable);

thread1.setName("Mickey");

thread2.setName("Pluto");

thread1.start();

thread2.start();

   }

}

 

 They run UtilityBillManager’s run code. These threads can modify the member variable amountDue because they both work on a common UtilityBill object. Note that the setName() is used to set the names of two threads. We are specifically naming the threads to identify them in the output. Thus, we will know exactly which thread was executing at that time.

 

@

If you do not explicitly set the name of thread, Java assigns the names such as Thread-1, Thread-2 and so on. Whenever your are executing a Java code, you can access the name of the thread that is executing that code by invoking Thread.currentThread().getName(). For example if you make this call in main() method, it will return “main” as the name of currently executing thread.

 

Once thread1 (Mickey) and thread2 (Pluto) are started, they both will try to execute the run method of same UtilityBillManager object. In other words, they both are trying to pay the utility bill of the same account. If we assume that the thread Mickey gets its turn first, it will enter the run method and print the message “Mickey is going to pay the bill”. After that it checks whether the amount due is payable with condition (account.getAmountDue() > 0). Since nobody has paid the bill yet, it will be true. After checking that amount is indeed due, the thread Mickey will go to sleep. Now there is a good chance that the thread Pluto gets its turn. It will also check that amount is really due and prints “Pluto is going to pay the bill”. It also goes to sleep then. Meantime the thread  Mickey might wake up as its sleeping period is over and pay the bill. Eventually when the thread Pluto wakes up, he will be unaware that Mickey already paid the bill while he was sleeping. Hence, he too will pay the bill. Thus despite the check that amount is indeed due, there will be double payment. You might get output some thing like this…

 

Mickey is going to pay the bill

Pluto is going to pay the bill

Mickey paid the bill

Pluto paid the bill

Double payment of the bill!

 

Now if somehow the thread Pluto gets its turn first and pays the bill, Mickey might still pay it again. In that case, the output will be…

 

Pluto is going to pay the bill

Mickey is going to pay the bill

Pluto paid the bill

Mickey paid the bill

Double payment of the bill!

 

@

We have introduced the call to sleep() just to increase the chances of race condition between two threads. Since our example is simplified version of real application, we do not have any complex operations while paying the bill. So we simply introduced a call to sleep() to simulate those complexities.

 

If you remove the call to sleep after condition check, it is possible that Mickey (or Pluto) might pay the bill before the other thread checks the condition. In that case, the amount due will be zero and hence, the second thread can see that amount is not due and hence it will not try to pay the bill again and output will be either:

 

Mickey is going to pay the bill

Mickey paid the bill

Or

Pluto is going to pay the bill

Pluto paid the bill

 

Thus, the code may or may not work correctly. When the threads go to sleep a while, it gives you a fair idea what might went wrong. The bill might be paid twice because two threads are trying to pay it, unaware of the changes made by each other. It is also possible that you will get lucky and the bill will be paid correctly. However, for reliable solution, you have to think about worst-case scenarios. In worst case, you have seen how the bill is paid twice. This kind of inconsistency is unacceptable in real applications. Therefore, what can be you do to ensure that the bill is paid correctly even when two (or more) threads are simultaneously trying to pay it?