Synchronization


One of the biggest pitfalls with multithreaded development is dealing with the fact that threads run in nondeterministic order. Each time you execute a multithreaded application, the threads may run in a different order that is impossible to predict. The most challenging result of this nondeterminism is that you may have coded a defect that surfaces only once in a few thousand executions, or once every second full moon.[8]

[8] The subtle gravitational pull might cause electrostatic tides that slow the processor for a picosecond.

Two threads that are executing code do not necessarily move through the code at the same rate. One thread may execute five lines of code before another has even processed one. The thread scheduler interleaves slices of code from each executing thread.

Also, threading is not at the level of Java statements, but at a lower level of the VM operations that the statements translate down to. Most statements you code in Java are non-atomic: The Java compiler might create several internal operations to correspond to a single statement, even something as simple as incrementing a counter or assigning a value to a reference. Suppose an assignment statement takes two internal operations. The second operation may not complete before the thread scheduler suspends the thread and executes a second thread.

All this code interleaving means that you may encounter synchronization issues. If two threads go after the same piece of data at the same time, the results may not be what you expect.

You will modify the sis.studentinfo.Account class to support withdrawing funds. To withdraw funds, the balance of the account must be at least as much as the amount being withdrawn. If the amount is too large, you should do nothing (for simplicity's and demonstration's sake). In any case, you never want the account balance to go below zero.


 package sis.studentinfo; // ... public class AccountTest extends TestCase {    // ...    private Account account;    protected void setUp() {       account = new Account();       // ...    }    // ...    public void testWithdraw() throws Exception {       account.credit(new BigDecimal("100.00"));       account.withdraw(new BigDecimal("40.00"));       assertEquals(new BigDecimal("60.00"), account.getBalance());    }    public void testWithdrawInsufficientFunds() {       account.credit(new BigDecimal("100.00"));       account.withdraw(new BigDecimal("140.00"));       assertEquals(new BigDecimal("100.00"), account.getBalance());    }    // ... } 

The withdraw method:

 package sis.studentinfo; // ... public class Account implements Accountable {    private BigDecimal balance = new BigDecimal("0.00");    // ...    public void withdraw(BigDecimal amount) {       if (amount.compareTo(balance) > 0)          return;       balance = balance.subtract(amount);    }    // ... } 

A fundamental problem exists with the withdraw method in a multithreaded environment. Suppose two threads attempt to withdraw $80 from an account with a $100 balance at the same time. One thread should succeed; the other thread should fail. The end balance should be $0. However, the code execution could interleave as follows:

Thread 1

Thread 2

Bal

amount.compareTo(balance) > 0

 

100

 

amount.compareTo(balance) > 0

100

balance = balance.subtract(amount)

 

20

 

balance = balance.subtract(amount)

-60


The second thread tests the balance after the first thread has approved the balance but before the first thread has subtracted from the balance. Other execution sequences could cause the same problem.

You can write a short test to demonstrate this problem.

 package sis.studentinfo; import junit.framework.*; import java.math.BigDecimal; public class MultithreadedAccountTest extends TestCase {    public void testConcurrency() throws Exception {       final Account account = new Account();       account.credit(new BigDecimal("100.00"));       Thread t1 = new Thread(new Runnable() {          public void run() {             account.withdraw(new BigDecimal("80.00"));          }});       Thread t2 = new Thread(new Runnable() {          public void run() {             account.withdraw(new BigDecimal("80.00"));          }});       t1.start();       t2.start();       t1.join();       t2.join();       assertEquals(new BigDecimal("20.00"), account.getBalance());    } } 

There's definitely some code redundancy in this method. After you understand what the method is doing, make sure you refactor it to eliminate the duplication.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net