This lab exercise will give you practice working with Java threads, which are covered in Chapter 9 of the text book.

You will take a set of classes that simulate the use of a bank account by multiple users and modify it to give the simulation realistic, multi-threaded behavior.

Basic Requirements

The program allows control of the following aspects of a bank account: We imagine a scenario in which a number of siblings are given access to a bank account by a parent.

For each simulation run:

In the version of the program you are given, the siblings' transaction lists are processed in series — a sibling carries out all of the transactions on its list before another sibling can carry out its transactions.

This creates two deficiencies:

In the Current Program (see menu for applet):
You will correct these deficiences by making each sibling's transaction processing occur in a separate thread, making sure to avoid potential race conditions resulting in an incorrect balance.

Additionally, to avoid the deadlock situation that occurs when all siblings attempt illegal transactions, you will add a parent thread that "rescues" the siblings from deadlock by making deposits when appropriate.

In the Desired Program (see menu for applet):

  • A high starting balance will make a parent unnecessary, but the siblings' transactions will be carried out in parallel, with it possibly happening that one sibling "rescues" another from an illegal withdrawal.
  • A lower starting balance increases the chance of a parent rescue.
  • The parent rescues with (possibly repeated) deposits of $100
  • The parent is only involved when:
    • The siblings are deadlocked
    • All siblings have completed their transactions, in which case the parent thread terminates by attempting to withdraw a zero amount
  • No situation results in any sibling's not carrying out all of its transactions.
This program uses the Model-View-Controller (MVC) software architecture where:
  • Model: The bank account
  • View: The transaction log
  • Controller: The configuration combo boxes and run button
The MVC architecture is supported by Java's Observer interface and Observable class in java.util.

These two types comprise the Observer design pattern:

The bank account program instantiates the Observer design pattern through the BankAccount and LogView classes:

Here is the full class diagram for this program, leaving out the foundation types:

The source code for the classes used in this program is shown in this section.

To run the program, run the BankAccountFrame.java file.

The zipped NetBeans project to use for this lab is here: CS_2511_Threads.zip.

Unzip the project folder and open the resulting CS_2511_Threads project in NetBeans.

The exercise will proceed in three steps:

  1. Thread the program
  2. Synchronize the threads
  3. Rescue the threads from deadlocks
The BankAccountUser class has code in its run method that we want to run in a separate thread.

The BankAccountControl class constructs a RUN button whose action listener loops through the BankAccountUser objects and calls their run methods in succession.

In this step, you will simply thread the program (without synchronizing) and observe the results.

Here is the simplest way to make the BankAccountUser class run methods execute in separate threads:
  • Declare BankAccountUser to extend the Thread class
  • Since the Thread class already has a getName method, remove this method from BankAccountUser and, in the constructor, replace the statement "this.name = name;" to "super(name);"
  • At the bottom of BankAccountUser's run method's while loop, just before repeating, sleep for, say, 100 milliseconds — this will require handling a possible InterruptedException
  • In BankAccountControl, find where the button listener loops through the users and change the call run() to start()
Now try running the simulation.
  • For one sibling, the behavior will be fine unless an illegal withdrawal is attempted, in which case an exception is thrown and the simulation halts.
  • For multiple siblings, one or more of the following scenarios will likely occur:
    • Output will be corrupted as messages are not able to complete
    • Runtime exceptions occur due to illegal withdrawals
    • Assertion errors occur due to race conditions as deposits and withdrawals are interrupted
To solve these problems, the threads must be synchronized.
In this step you will eliminate the runtime exceptions and assertion errors by synchronizing the threads using the Object class's wait() and notifyAll() methods, and observe the results.
Runtime exceptions and assertion errors originate in the withdraw and deposit methods of the BankAccount class, so:
  • For each of these methods add the synchronized modifier to the method's declaration
  • In withdraw, instead of throwing an exception when an illegal withdrawal is attempted, make a call to wait() while the withdrawal amount is greater than the balance
  • Declare the withdraw method to throw InterruptedException (which is handled by the BankAccountUser's run method)
  • At the end of the deposit method, just before the assertion, make a call to notifyAll()
Results: Assertion errors from race conditions and illegal withdrawals are avoided, but deadlocks can result, with one or more siblings not finishing their transactions.

Here is the log of a run with an initial balance of 100 dollars, 3 siblings, and a 100 dollar amount limit:

      Sibling 1 Withdrawing $84. Balance = 16
      Sibling 2 Depositing $34. Balance = 50
      Sibling 3 Depositing $7. Balance = 57
      Sibling 1 Depositing $12. Balance = 69
      Sibling 2 Depositing $13. Balance = 82
      Sibling 3 Withdrawing $23. Balance = 59
      Sibling 1 Withdrawing $97                              1
      Sibling 2 Depositing $57. Balance = 116. Balance = 19  2
      Sibling 3 Withdrawing $95                              3
      Sibling 2 Withdrawing $99
      Sibling 1 Depositing $8. Balance = 27                  4
      Sibling 1 Withdrawing $78                              5

        
In this run,
  • Sibling 1 tries to make a withdrawal ($97) greater than the balance ($59), so it waits
  • Sibling 2 makes a deposit, lifting the balance sufficiently, so sibling 1's withdrawal completes
  • Siblings 3 and 2 both try to withdraw more than the balance, so they wait
  • Sibling 1 makes a deposit, but not enough for siblings 2 and 3
  • Sibling 1 tries to withdraw more than the balance, so deadlock results
In this step you will add a thread whose sole purpose is to rescue the sibling threads from deadlock.

This thread, analogous to the siblings' parent, will be executed by the run method of a new BankAccountRescuer class, which will extend BankAccountUser.

The BankAccountRescuer thread will:

  • Detect when all siblings are waiting to make a withdrawal
  • Deposit $100 when all siblings are waiting
  • Deposit $0 and terminate when all siblings are finished (the dummy deposit is so the BankAccount object can log a message when the parent thread has finished)
Upon completion of this step the overall class diagram will look like:

Note how the "finished" status of a BankAccountUser object is managed:
  • BankAccountUser has boolean fields oneMore and finished with accompanying getters and setters
  • The BankAccountUser constructor initializes oneMore and finished to false
  • The BankAccountUser run method sets oneMore to true when it is about to make its last transaction
  • The BankAccount class's deposit and withdraw methods check the oneMore status of the user, setting the user's finished field to true after the last transaction is complete
You can manage the waiting status of a user in a similar way:
  • Add a boolean waiting field with setter and getter to BankAccountUser
  • Initialize waiting to false
  • In the BankAccount class's withdraw method:
    • When the user tries to withdraw more than the balance, the user's waiting field is set to true before entering the wait() loop
    • When the loop is finished, the waiting field is set to false
  • Create a new class called BankAccountRescuer that extends BankAccountUser
  • Create a BankAccountRescuer constructor that:
    • Takes a name, a BankAccount, and an array of BankAccountUsers as parameters
    • Appropriately calls super (can send a null list of transactions) and stores the user array
  • Suggestion: write private allFinished() and allWaiting() methods that loop through the user array and return the appropriate boolean value
  • Override the run method to loop until all users (siblings) have finished:
    • Inside the loop, check if all users are waiting, and deposit $100 if they are
    • Like any thread loop, it should occasionally sleep
  • When all siblings have finished their transactions, the parent thread should terminate, after:
    • Setting its oneMore status to true
    • Depositing $0 in the account
The BankAccountControl class creates and starts all the sibling threads.

To add the parent thread:

  • Just after the loop that creates the BankAccountUser array of siblings, create a new BankAccountRescuer object with name "Parent"
  • Just after the loop that starts each of the sibling threads, start the parent thread
  • In the allFinished method, also check that the parent is finished (so the parent's run method can exit)
The program should now display the desired behavior.
When finished, zip your project folder as your-login-LEX13.zip.

Email the zip file to your TA.

Successful completion of:
  • Step 1 — Add Threads: 2 points
  • Step 2 — Synchronize Threads: 3 points
  • Step 3 — Avoid Deadlocks: 5 points